diff --git a/.gitignore b/.gitignore index dedc00d..fd2f4a6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ __pycache__/ # Generated build/test output; keep packaging source and shipping exe files. /build/actions/ +/build/artifacts/ +/build/hell-tower/ +/build/test-deps/ /build/arrival/ /build/billiards/ /build/card-ranks/ diff --git a/bin/poker.exe b/bin/poker.exe index 1056969..2ab52e4 100644 Binary files a/bin/poker.exe and b/bin/poker.exe differ diff --git a/build/AssemblerCasinoLauncher.cs b/build/AssemblerCasinoLauncher.cs index c7a5a7f..bc33e8d 100644 --- a/build/AssemblerCasinoLauncher.cs +++ b/build/AssemblerCasinoLauncher.cs @@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher { static class Program { - const string ExpectedPokerSha256 = "755067F511E82184081FE3D06391FCF872EC058AB224CA6ED8C1DB0A2990E1BA"; + const string ExpectedPokerSha256 = "A3A35CB3C9F21FB0F29F2849B01DFEE914AEA7830ED7540FFEF24C6A8467F44A"; [STAThread] static void Main() diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index 1056969..2ab52e4 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/casino3d.inc b/casino3d.inc index 38e08e4..18e5180 100644 --- a/casino3d.inc +++ b/casino3d.inc @@ -3283,6 +3283,7 @@ dice_check_win: shl eax, 1 add bankroll, eax mov dice3d_win, eax + invoke TriggerCharmWinBonus,dice3d_bet mov dice3d_state, 2 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 invoke NpcRecordRound3D,8,1 @@ -5373,12 +5374,12 @@ check_slots_mode: call RenderJackbox3D jmp present_frame check_jackbox_done: - cmp engineMode,MODE_DARTS - jne check_darts_done - call HandleDarts3D - call RenderDarts3D - jmp present_frame -check_darts_done: + cmp engineMode,MODE_DARTS + jne check_darts_done + call HandleDarts3D + call RenderDarts3D + jmp present_frame +check_darts_done: cmp engineMode,MODE_FORTUNE jne @F call HandleFortuneDialog3D @@ -5659,7 +5660,7 @@ engine_shutdown: call SaveProgress3D invoke PlayCasinoSound3D, 0, 0, 0 call FreeRussianAssets3D - call FreeDarts3D + call FreeDarts3D call FreeStartupAssets3D call FreeCardTableAssets3D call FreeBuckshotAssets3D @@ -5687,3 +5688,7 @@ IFDEF CASINO_ACTION_TEST include tools\tests\casino_actions_test.inc ENDIF + +IFDEF ARTIFACT_TEST +include tools\tests\artifacts_test.inc +ENDIF diff --git a/dist/Assembler Casino.exe b/dist/Assembler Casino.exe index a37e9ac..77929d3 100644 Binary files a/dist/Assembler Casino.exe and b/dist/Assembler Casino.exe differ diff --git a/docs/ARTIFACTS_AND_TOWER.md b/docs/ARTIFACTS_AND_TOWER.md new file mode 100644 index 0000000..c18cd44 --- /dev/null +++ b/docs/ARTIFACTS_AND_TOWER.md @@ -0,0 +1,97 @@ +# Артефакты и Адская башня + +## Магазин Сидоровича + +Магазин открывается на вкладке артефактов. Стрелки вверх/вниз выбирают предмет, +`E` включает или выключает эффект. Покупка требует двух нажатий `Enter`: +первое запрашивает подтверждение, второе оплачивает набор. Смена выбранного +предмета отменяет подтверждение. Поддерживается мышь. `Tab` возвращает рубашки, +`Esc` — холл. + +| Артефакт | Цена набора | Зарядов | Эффект | +|---|---:|---:|---| +| Клевер | 900 | 3 | Возвращает 50% фактической потери раунда, округляя вверх; максимум 10000 | +| Подкова | 1800 | 5 | Добавляет 35% чистой прибыли; максимум 5000, минимум 1 при положительной прибыли | +| Череп | 3500 | 1 | Отменяет смертельный выстрел в русской рулетке или Buckshot | +| Контракт | 4000 | 2 | Добавляет 100% чистой прибыли, максимум 10000; проигрыш тоже расходует заряд | + +Лимит — 99 зарядов каждого вида. При недостатке денег или превышении лимита +набор не покупается даже частично. Заряды не имеют срока действия. +Выключенный эффект не расходуется. Контракт несовместим с клевером и подковой: +его включение выключает оба эффекта. Череп может работать вместе с контрактом. + +Возвращённая ставка не считается прибылью. Частичный проигрыш страхуется только +на сумму потери. Ничья не расходует денежные эффекты. Клевер и подкова расходуют +заряд только при фактическом зачислении. Банк ограничен 1000000000. +Режим наблюдателя не расходует инвентарь и не начисляет личные бонусы. + +## Адская башня + +**Банк общий с профилем.** Ставки, проигрыши и выплаты меняют обычный банк; +выход не восстанавливает прежнюю сумму. Денежный приз за шестой этаж — 66600, +однократно, с общим ограничением банка. + +Обычные артефакты внутри башни не работают и не расходуются. Вместо них перед +стартом и после каждого из первых пяти этажей выбирается одна из трёх временных +реликвий. `1`/`2`/`3` берут соответствующую карточку; стрелки влево/вправо и +`Enter` выбирают выделенную. Также можно нажать карточку мышью. + +| Реликвия | Действие | +|---|---| +| Сердце | Один повтор любого проигранного этажа | +| Часы | Два повтора предстоящего этажа; остаток сгорает при переходе дальше | +| Патрон | Две отмены смертельного выстрела | +| Печать | Дважды удаляет последнюю карту, вызвавшую перебор в блэкджеке; затем ходит крупье | +| Око палача | Показывает игроку следующий патрон Buckshot на протяжении забега | +| Кошель | Дважды возвращает потерю проигранного этажа, максимум 5000 за возврат | + +Кошель не даёт дополнительную попытку. При поражении сначала возвращаются деньги, +затем расходуются часы соответствующего этажа, затем сердце. Если повторов нет, +забег завершается. Ничья в блэкджеке позволяет переиграть без расхода повтора. + +Предложения: сердце, кошель и ситуационная реликвия. Перед блэкджеком доступна +печать, перед русской рулеткой — патрон, перед Buckshot — око, иначе — часы. +Заряды остальных реликвий складываются. Выбор нельзя получить повторным нажатием. + +Все реликвии сбрасываются при победе, окончательном поражении, выходе в холл +и начале нового забега. В сохранение они не записываются. Выход во время +оплаченного раунда соблюдает правила соответствующей игры: например, монета +доигрывает результат, а карточный стол считает незавершённую руку проигранной. + +Счётчики `СЕР`, `ЧАС`, `ПАТ`, `ПЕЧ`, `ОКО`, `КОШ` показывают остаток реликвий. +Внутри игры они находятся сверху, между этажами — снизу. + +## Сохранения + +Именованный профиль: 312 байт. Первые 280 байт сохраняют старый формат и подпись; +расширение `ARF2` хранит четыре счётчика, маску включения, версию и контрольную +сумму. Старый профиль загружается с пустым новым инвентарём. +При смене или создании профиля инвентарь сбрасывается перед загрузкой. +Preview-сохранение: 40 байт, версия `C3D4`, маска эффектов и контрольная сумма; +старые preview-версии по-прежнему читаются. + +Контрольные суммы обнаруживают случайное повреждение; это не криптографическая +защита от редактирования владельцем компьютера. + +## Проверки и ограничения + +```powershell +python -B tools/tests/check_artifacts.py +python -B tools/tests/test_hell_tower_runthrough.py +python -B tools/tests/check_casino_actions.py +python -B tools/tests/check_hall_expansion.py +``` + +Для общих тестов нужны Pillow, treys и capstone. Это офлайн-зависимости тестов, +не зависимости игры. Игра и все перечисленные эффекты реализованы на MASM x86. + +Тест артефактов исполняет настоящие процедуры игры, проверяет покупки, лимиты, +отключение, чистую прибыль Buckshot, повторные выплаты, реликвии, выходы, +сохранения и их повреждение. Снимки UI и отчёт — `build/artifacts/`. +Проверяются длины строк карточек и очистка предыдущего кадра. + +Шестиступенчатый smoke-тест действительно открывает игровые сцены, но подставляет +победные исходы. Он **не измеряет вероятность прохождения**. Целевые 40–60% +прохождений освоившим правила игроком пока не подтверждены статистикой; +полноценная оценка требует серий забегов с указанной стратегией на каждом этаже. +Эта переработка добавляет страховку и повторы, но не подменяет случайные исходы. diff --git a/docs/GRAPHICAL_SCENES.md b/docs/GRAPHICAL_SCENES.md index d89084c..c469073 100644 --- a/docs/GRAPHICAL_SCENES.md +++ b/docs/GRAPHICAL_SCENES.md @@ -97,8 +97,9 @@ and reads before publishing the pixels. Packs are loaded once and reused. First-person roulette actions need approved, consistent hand/gun animation sequences. The requested replacement sheet failed generation and is not shipped. Multiway poker requires actual 3/4-player betting, side pots and AI seat state, -not decorative extra opponents. Native talisman effects, achievements, daily -tasks and online tables still require integration with the selected profile. +not decorative extra opponents. Native talisman effects were subsequently +integrated with selected profiles; see `ARTIFACTS_AND_TOWER.md`. Achievements, +daily tasks and online tables have separate integration work. The old installer has not been repackaged with this work. The classic 777 cabinet is now playable natively: generated cabinet/symbol art, diff --git a/docs/native-clover.md b/docs/native-clover.md index 11ca3d4..5d9b430 100644 --- a/docs/native-clover.md +++ b/docs/native-clover.md @@ -61,7 +61,8 @@ idempotent payout, bank cap, missing funds and six framebuffer captures. `check_clover.py --parity` checks assets, real framebuffer clipping, all fifteen complete cells, selected paid-mask changes and tested/shipping assembly code. -Native talisman modifiers and legacy console achievement/dance side effects +Native talisman modifiers were subsequently integrated; see +`ARTIFACTS_AND_TOWER.md`. Legacy console achievement/dance side effects are not ported by this change. NPC challenges count native slot rounds and classify only positive net results as wins. No claim of completing every legacy Clover feature or its balance is made. diff --git a/docs/native-scene-fixes.md b/docs/native-scene-fixes.md index 14ccdf3..b17cc2a 100644 --- a/docs/native-scene-fixes.md +++ b/docs/native-scene-fixes.md @@ -1,13 +1,16 @@ # Native scene fixes -The MASM game stays in one graphical window. Unported CloverPit, -online roulette, Umamusume, tower, terminal and observer interactions open a +The MASM game stays in one graphical window. Unported +online roulette and Umamusume 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 console routine or blocks the window event loop. Legacy console game code is retained for the earlier standalone console mode, not called by these graphical interactions. +CloverPit, tower, terminal and observer subsequently received native scenes. +See `ARTIFACTS_AND_TOWER.md` for current artifact and tower rules. + 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. diff --git a/docs/native-tables.md b/docs/native-tables.md index f9e55c1..c991afb 100644 --- a/docs/native-tables.md +++ b/docs/native-tables.md @@ -82,6 +82,8 @@ Existing fullscreen, startup, audio, dice and 2000 roulette-round checks remain. This is not a completed migration of the whole casino. The implemented graphical games include the three card tables, Russian roulette, Buckshot, multi-bet casino roulette, coin, dice, skulls, skill checks and billiards. -777, CloverPit, online play, tower, terminal and observer still show native -unavailable panels. Native profile data remains separate from legacy console +777, CloverPit, tower, terminal and observer subsequently received native +implementations. Only online roulette and Umamusume still show unavailable +panels. See `ARTIFACTS_AND_TOWER.md` for artifact and tower rules. +Native profile data remains separate from legacy console account saves. The old installer is not refreshed by this change. diff --git a/poker.asm b/poker.asm index 3ebe31f..8affb54 100644 --- a/poker.asm +++ b/poker.asm @@ -1,6 +1,8 @@ .386 .model flat, stdcall option casemap:none +ArtifactSaveSignature3D PROTO :DWORD +ValidateArtifactSave3D PROTO :DWORD includelib kernel32.lib includelib winmm.lib @@ -24,7 +26,9 @@ FILE_ATTRIBUTE_NORMAL equ 80h INVALID_HANDLE_VALUE equ -1 SAVE_MAGIC equ 31435653h SAVE_SIGN_SIZE equ 276 -SAVE_FILE_SIZE equ 280 +SAVE_FILE_SIZE equ 312 +SAVE_LEGACY_SIZE equ 280 +SAVE_ARTIFACT_SIGN_SIZE equ 308 SAVE_SALT equ 0A57C0DEh ENABLE_VIRTUAL_TERMINAL_PROCESSING equ 0004h COLOR_DEFAULT equ 07h @@ -576,6 +580,7 @@ ReadKey ENDP ResetProfileDefaults PROC uses ecx edi + call ResetArtifacts3D mov bankroll, 10000 mov targetBank, 50000 mov gamesPlayed, 0 @@ -804,6 +809,20 @@ save_stats_played_loop: loop save_stats_played_loop call CalcSaveSignature mov DWORD PTR [saveBuffer+SAVE_SIGN_SIZE], eax + mov DWORD PTR [saveBuffer+280],32465241h ; ARF2 + mov eax,charmCloverUses + mov DWORD PTR [saveBuffer+284],eax + mov eax,charmHorseshoeUses + mov DWORD PTR [saveBuffer+288],eax + mov eax,charmSkullUses + mov DWORD PTR [saveBuffer+292],eax + mov eax,charmDevilUses + mov DWORD PTR [saveBuffer+296],eax + mov eax,charmEnabledMask3D + mov DWORD PTR [saveBuffer+300],eax + mov DWORD PTR [saveBuffer+304],2 + invoke ArtifactSaveSignature3D,OFFSET saveBuffer + mov DWORD PTR [saveBuffer+308],eax call BuildSaveFullPath invoke CreateFileA, OFFSET saveFullPath, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 cmp eax, INVALID_HANDLE_VALUE @@ -816,6 +835,7 @@ save_done: SaveGame ENDP LoadSave PROC uses eax ecx esi edi + call ResetArtifacts3D mov dailyDateKey, 0 mov dailyRewardMask, 0 mov dailyProgressPacked, 0 @@ -844,12 +864,29 @@ load_file_opened: mov eax, DWORD PTR [saveBuffer] cmp eax, SAVE_MAGIC jne load_done - cmp charsRead, SAVE_FILE_SIZE + cmp charsRead, SAVE_LEGACY_SIZE jb load_signature_ok mov edx, DWORD PTR [saveBuffer+SAVE_SIGN_SIZE] call CalcSaveSignature cmp eax, edx jne load_tampered + cmp charsRead,SAVE_LEGACY_SIZE + je load_signature_ok + cmp charsRead,SAVE_FILE_SIZE + jne load_tampered + invoke ValidateArtifactSave3D,OFFSET saveBuffer + test eax,eax + jz load_tampered + mov eax,DWORD PTR [saveBuffer+284] + mov charmCloverUses,eax + mov eax,DWORD PTR [saveBuffer+288] + mov charmHorseshoeUses,eax + mov eax,DWORD PTR [saveBuffer+292] + mov charmSkullUses,eax + mov eax,DWORD PTR [saveBuffer+296] + mov charmDevilUses,eax + mov eax,DWORD PTR [saveBuffer+300] + mov charmEnabledMask3D,eax load_signature_ok: mov eax, DWORD PTR [saveBuffer+4] mov bankroll, eax @@ -1905,8 +1942,8 @@ CheckDurakEnd ENDP include casino3d.inc -IFDEF CASINO_ACTION_TEST -RunCasinoActionsTest3D PROC uses ebx esi edi +IFDEF HELL_TOWER_TEST +RunHellTowerTest3D PROC uses ebx esi edi local stageLoop:DWORD call InitPaths3D @@ -1914,8 +1951,12 @@ RunCasinoActionsTest3D PROC uses ebx esi edi test eax, eax jz test_error_init + ; Test-owned session: no persistence or profile money is loaded. + mov arrivalConfirmed3D,0 + mov bankroll,10000 ; Enter Hell Tower call InitHellTower3D + invoke ChooseTowerRelic3D,0 cmp tournamentMode, 1 jne test_error_tower_mode @@ -1947,6 +1988,7 @@ stage_test_loop: call Present3D ; Advance to next stage + invoke ChooseTowerRelic3D,0 inc towerStage mov towerFloorState, TOWER_STATE_BRIEFING mov towerCalloutAnim, 0 @@ -1998,10 +2040,18 @@ test_error_exit_mode: test_error_exit_engine: mov eax, 107 ret -RunCasinoActionsTest3D ENDP +RunHellTowerTest3D ENDP ENDIF main PROC +IFDEF HELL_TOWER_TEST + call RunHellTowerTest3D + invoke ExitProcess,eax +ENDIF +IFDEF ARTIFACT_TEST + call RunArtifactTest3D + invoke ExitProcess,eax +ENDIF IFDEF CASINO_ACTION_TEST call RunCasinoActionsTest3D invoke ExitProcess, eax diff --git a/poker.exe b/poker.exe index 1056969..2ab52e4 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/poker.obj b/poker.obj index 11f7c05..d4e00ec 100644 Binary files a/poker.obj and b/poker.obj differ diff --git a/src_graphics/artifact_shop.inc b/src_graphics/artifact_shop.inc new file mode 100644 index 0000000..6195a91 --- /dev/null +++ b/src_graphics/artifact_shop.inc @@ -0,0 +1,180 @@ +HandleArtifactShop3D PROC uses ebx + invoke Pressed3D,09h + test eax,eax + jz @F + mov merchantTab3D,0 + mov artifactBuyArmed3D,0 + ret +@@: + invoke Pressed3D,26h + test eax,eax + jz @F + cmp merchantCharmSelected3D,0 + je @F + dec merchantCharmSelected3D + mov artifactBuyArmed3D,0 + mov merchantMessage3D,0 +@@: + invoke Pressed3D,28h + test eax,eax + jz @F + cmp merchantCharmSelected3D,3 + jae @F + inc merchantCharmSelected3D + mov artifactBuyArmed3D,0 + mov merchantMessage3D,0 +@@: + invoke Pressed3D,45h + test eax,eax + jz artifact_shop_mouse + invoke ToggleArtifact3D,merchantCharmSelected3D + mov artifactBuyArmed3D,0 +artifact_shop_mouse: + cmp mouseClick3D,0 + je artifact_shop_enter + mov mouseClick3D,0 + cmp mouseY3D,27 + jb artifact_shop_enter + cmp mouseY3D,45 + jae artifact_shop_rows + cmp mouseX3D,96 + jae artifact_shop_enter + mov merchantTab3D,0 + ret +artifact_shop_rows: + cmp mouseX3D,8 + jb artifact_shop_enter + cmp mouseX3D,108 + jae artifact_shop_actions + mov eax,mouseY3D + sub eax,52 + js artifact_shop_enter + xor edx,edx + mov ecx,34 + div ecx + cmp eax,3 + ja artifact_shop_enter + mov merchantCharmSelected3D,eax + mov artifactBuyArmed3D,0 + mov merchantMessage3D,0 + ret +artifact_shop_actions: + cmp mouseX3D,116 + jb artifact_shop_enter + cmp mouseX3D,311 + jae artifact_shop_enter + cmp mouseY3D,169 + jb artifact_shop_enter + cmp mouseY3D,185 + jae artifact_shop_buy_mouse + invoke ToggleArtifact3D,merchantCharmSelected3D + mov artifactBuyArmed3D,0 + ret +artifact_shop_buy_mouse: + cmp mouseY3D,190 + jb artifact_shop_enter + cmp mouseY3D,210 + jae artifact_shop_enter + jmp artifact_shop_confirm +artifact_shop_enter: + invoke Pressed3D,0Dh + test eax,eax + jz artifact_shop_done +artifact_shop_confirm: + cmp artifactBuyArmed3D,0 + jne artifact_shop_purchase + mov artifactBuyArmed3D,1 + mov merchantMessage3D,0 + ret +artifact_shop_purchase: + mov artifactBuyArmed3D,0 + invoke BuyMerchantCharm3D,merchantCharmSelected3D +artifact_shop_done: + ret +HandleArtifactShop3D ENDP + +RenderArtifactShop3D PROC uses ebx esi edi + local y:DWORD,x:DWORD,ink:DWORD + invoke DrawBox,0,0,320,240,0015191Bh + invoke DrawBox,0,0,320,24,002B302Ah + invoke DrawString8x8,8,7,OFFSET artifactShopTitle3D,00EED5A0h + invoke FormatNumber,bankroll,OFFSET artifactNum3D + invoke DrawString8x8,232,7,OFFSET artifactNum3D,00AAEEAAh + invoke DrawBoxBorder,8,28,88,17,002B302Ah,00667766h,1 + invoke DrawString8x8,14,33,OFFSET merchantStock3D,00BBBBBBh + invoke DrawBoxBorder,100,28,212,17,003D4939h,00C8AE70h,1 + invoke DrawString8x8,122,33,OFFSET merchantTabCharmsLabel,00FFE0A0h + xor ebx,ebx + mov y,52 +artifact_shop_card: + mov ink,00465450h + cmp ebx,merchantCharmSelected3D + jne @F + mov ink,00D8B878h +@@: + invoke DrawBoxBorder,8,y,100,31,00232A2Bh,ink,1 + mov eax,y + add eax,5 + invoke DrawString8x8,13,eax,charmNames3D[ebx*4],artifactColors3D[ebx*4] + mov ecx,ebx + bt charmEnabledMask3D,ecx + mov esi,OFFSET artifactOff3D + jnc @F + mov esi,OFFSET artifactOn3D +@@: + mov eax,y + add eax,18 + invoke DrawString8x8,13,eax,esi,00AAAAAAh + lea edi,charmCloverUses + invoke FormatNumber,[edi+ebx*4],OFFSET artifactNum3D + mov eax,y + add eax,18 + invoke DrawString8x8,76,eax,OFFSET artifactNum3D,00FFFFFFh + add y,34 + inc ebx + cmp ebx,4 + jb artifact_shop_card + mov ebx,merchantCharmSelected3D + invoke DrawBoxBorder,114,52,198,115,001D2425h,00647770h,1 + invoke DrawString8x8,120,59,charmNames3D[ebx*4],artifactColors3D[ebx*4] + invoke DrawString8x8,120,77,charmDescs3D[ebx*4],00FFFFFFh + ; Detail descriptions use full width below cards to avoid clipped text. + invoke DrawString8x8,8,216,artifactRules3D[ebx*4],00DDCDAAh + invoke DrawString8x8,8,228,OFFSET artifactHelp3D,0099AAA5h + invoke DrawString8x8,120,96,OFFSET artifactPack3D,0099AAA5h + invoke FormatNumber,charmUsesPack3D[ebx*4],OFFSET artifactNum3D + invoke DrawString8x8,280,96,OFFSET artifactNum3D,00FFFFFFh + invoke DrawString8x8,120,112,OFFSET artifactStock3D,0099AAA5h + lea edi,charmCloverUses + invoke FormatNumber,[edi+ebx*4],OFFSET artifactNum3D + invoke DrawString8x8,280,112,OFFSET artifactNum3D,00FFFFFFh + invoke DrawString8x8,120,128,OFFSET artifactPrice3D,0099AAA5h + invoke FormatNumber,charmPrices3D[ebx*4],OFFSET artifactNum3D + invoke DrawString8x8,264,128,OFFSET artifactNum3D,00FFD080h + invoke DrawString8x8,120,150,artifactUseRules3D[ebx*4],00BBCCC0h + mov esi,OFFSET artifactTowerNote3D + cmp merchantMessage3D,1 + jne @F + mov esi,OFFSET artifactBought3D +@@: + cmp merchantMessage3D,2 + jne @F + mov esi,OFFSET merchantPoor3D +@@: + cmp merchantMessage3D,3 + jne @F + mov esi,OFFSET artifactLimit3D +@@: + ; Long status uses a full-width strip, not the detail card. + invoke DrawString8x8,8,202,esi,00E0B580h + invoke DrawBoxBorder,116,170,194,15,00303E37h,006F9982h,1 + invoke DrawString8x8,128,174,OFFSET artifactToggle3D,00BBDDCCh + mov esi,OFFSET artifactBuy3D + cmp artifactBuyArmed3D,0 + je @F + mov esi,OFFSET artifactConfirm3D +@@: + invoke DrawBoxBorder,116,188,194,13,0043372Ah,00CCAA66h,1 + invoke DrawString8x8,120,191,esi,00FFE0A0h + ret +RenderArtifactShop3D ENDP diff --git a/src_graphics/artifact_system.inc b/src_graphics/artifact_system.inc new file mode 100644 index 0000000..a4c753c --- /dev/null +++ b/src_graphics/artifact_system.inc @@ -0,0 +1,239 @@ +; Artifact settlement owns caps, inventory and observer/tower isolation. +PreviewArtifactSignature3D PROC uses esi ecx source:DWORD + mov esi,source + mov ecx,9 + mov eax,0A57C0DEh +preview_artifact_sign: + rol eax,5 + xor eax,[esi] + add eax,9E3779B9h + add esi,4 + loop preview_artifact_sign + ret +PreviewArtifactSignature3D ENDP + +ArtifactSaveSignature3D PROC uses esi ecx edx source:DWORD + mov esi,source + mov ecx,SAVE_ARTIFACT_SIGN_SIZE/4 + mov eax,0A57C0DEh +artifact_sign_word: + rol eax,5 + xor eax,[esi] + mov edx,eax + shr edx,16 + xor eax,edx + add eax,9E3779B9h + add esi,4 + loop artifact_sign_word + ret +ArtifactSaveSignature3D ENDP + +ValidateArtifactSave3D PROC uses esi edi source:DWORD + mov esi,source + cmp DWORD PTR [esi+280],32465241h + jne artifact_save_invalid + cmp DWORD PTR [esi+304],2 + jne artifact_save_invalid + invoke ArtifactSaveSignature3D,esi + cmp eax,[esi+308] + jne artifact_save_invalid + mov edi,284 +@@: + cmp DWORD PTR [esi+edi],99 + ja artifact_save_invalid + add edi,4 + cmp edi,300 + jb @B + cmp DWORD PTR [esi+300],15 + ja artifact_save_invalid + mov eax,1 + ret +artifact_save_invalid: + xor eax,eax + ret +ValidateArtifactSave3D ENDP + +ArtifactCredit3D PROC amount:DWORD + mov eax,1000000000 + sub eax,bankroll + cmp eax,amount + jbe @F + mov eax,amount +@@: + add bankroll,eax + ret +ArtifactCredit3D ENDP + +ResetArtifacts3D PROC + mov charmCloverUses,0 + mov charmHorseshoeUses,0 + mov charmSkullUses,0 + mov charmDevilUses,0 + mov charmEnabledMask3D,7 + ret +ResetArtifacts3D ENDP + +ClampArtifacts3D PROC uses esi ecx + lea esi,charmCloverUses + mov ecx,4 +@@: + cmp DWORD PTR [esi],CHARM_MAX_USES + jbe artifact_clamp_next + mov DWORD PTR [esi],CHARM_MAX_USES +artifact_clamp_next: + add esi,4 + loop @B + and charmEnabledMask3D,15 + test charmEnabledMask3D,8 + jz @F + and charmEnabledMask3D,12 +@@: + ret +ClampArtifacts3D ENDP + +ToggleArtifact3D PROC index:DWORD + mov ecx,index + cmp ecx,3 + ja artifact_toggle_done + btc charmEnabledMask3D,ecx + cmp ecx,3 + jne @F + test charmEnabledMask3D,8 + jz artifact_toggle_save + and charmEnabledMask3D,12 + jmp artifact_toggle_save +@@: + cmp ecx,2 + je artifact_toggle_save + and charmEnabledMask3D,7 +artifact_toggle_save: + call SaveProgress3D +artifact_toggle_done: + ret +ToggleArtifact3D ENDP + +TriggerCharmLossProtection PROC uses ebx ecx edx betAmount:DWORD + cmp obsActive3D,0 + jne artifact_loss_none + cmp tournamentMode,0 + jne artifact_loss_none + cmp betAmount,0 + je artifact_loss_none + test charmEnabledMask3D,8 + jz artifact_loss_clover + cmp charmDevilUses,0 + je artifact_loss_none + dec charmDevilUses + call SaveProgress3D + jmp artifact_loss_none +artifact_loss_clover: + test charmEnabledMask3D,1 + jz artifact_loss_none + cmp charmCloverUses,0 + je artifact_loss_none + mov eax,betAmount + inc eax + shr eax,1 + cmp eax,10000 + jbe @F + mov eax,10000 +@@: + invoke ArtifactCredit3D,eax + test eax,eax + jz artifact_loss_none + dec charmCloverUses + invoke CopyString3D,OFFSET msgCharmCloverSave,OFFSET flashMessage + mov flashTimer,140 + call SaveProgress3D + mov eax,1 + ret +artifact_loss_none: + xor eax,eax + ret +TriggerCharmLossProtection ENDP + +; Input is NET profit, never gross payout or returned stake. +TriggerCharmWinBonus PROC uses ebx ecx edx winAmount:DWORD + cmp obsActive3D,0 + jne artifact_bonus_none + cmp tournamentMode,0 + jne artifact_bonus_none + cmp winAmount,0 + je artifact_bonus_none + test charmEnabledMask3D,8 + jz artifact_bonus_horse + cmp charmDevilUses,0 + je artifact_bonus_none + mov eax,winAmount + cmp eax,10000 + jbe @F + mov eax,10000 +@@: + invoke ArtifactCredit3D,eax + test eax,eax + jz artifact_bonus_none + dec charmDevilUses + mov ebx,eax + invoke CopyString3D,OFFSET msgCharmDevilSave,OFFSET flashMessage + jmp artifact_bonus_save +artifact_bonus_horse: + test charmEnabledMask3D,2 + jz artifact_bonus_none + cmp charmHorseshoeUses,0 + je artifact_bonus_none + mov eax,winAmount + mov ecx,35 + mul ecx + mov ecx,100 + div ecx + test eax,eax + jnz @F + inc eax +@@: + cmp eax,5000 + jbe @F + mov eax,5000 +@@: + invoke ArtifactCredit3D,eax + test eax,eax + jz artifact_bonus_none + dec charmHorseshoeUses + mov ebx,eax + invoke CopyString3D,OFFSET msgCharmHorseshoeWin,OFFSET flashMessage +artifact_bonus_save: + mov flashTimer,140 + call SaveProgress3D + mov eax,ebx + ret +artifact_bonus_none: + xor eax,eax + ret +TriggerCharmWinBonus ENDP + +TriggerCharmBulletProtection PROC + cmp obsActive3D,0 + jne artifact_bullet_none + cmp tournamentMode,0 + je artifact_bullet_normal + cmp towerBulletUses3D,0 + je artifact_bullet_none + dec towerBulletUses3D + invoke CopyString3D,OFFSET towerBulletMessage3D,OFFSET flashMessage + mov flashTimer,140 + mov eax,1 + ret +artifact_bullet_normal: + test charmEnabledMask3D,4 + jz artifact_bullet_none + cmp charmSkullUses,0 + je artifact_bullet_none + dec charmSkullUses + invoke CopyString3D,OFFSET msgCharmSkullSave,OFFSET flashMessage + mov flashTimer,140 + call SaveProgress3D + mov eax,1 + ret +artifact_bullet_none: + xor eax,eax + ret +TriggerCharmBulletProtection ENDP diff --git a/src_graphics/buckshot.inc b/src_graphics/buckshot.inc index 0758298..5f15329 100644 --- a/src_graphics/buckshot.inc +++ b/src_graphics/buckshot.inc @@ -193,6 +193,7 @@ bs_commit_cleanup: mov bs3d_damage[edi*4], 1 mov bs3d_known, -1 mov bs3d_known[4], -1 + call UpdateTowerEye3D mov bs3d_messageTime, 1800 bs_commit_done: ret @@ -236,6 +237,9 @@ bs_finish_win: jbe @F mov bankroll, 1000000000 @@: + mov eax,bs3d_bet + shl eax,1 + invoke TriggerCharmWinBonus,eax invoke NpcRecordRound3D,7,1 call SaveProgress3D ret @@ -244,6 +248,7 @@ bs_finish_lose: mov bs3d_result, BS_LOSE mov bs3d_victim, 0 mov bs3d_timer, 800 + invoke TriggerCharmLossProtection,bs3d_bet invoke NpcRecordRound3D,7,0 call SaveProgress3D bs_finish_done: @@ -866,6 +871,7 @@ ResolveBuckshotPose3D ENDP RenderBuckshot3D PROC uses ebx esi edi local handY:DWORD,pose:DWORD,actor:DWORD + call UpdateTowerEye3D mov animOpacity3D, 255 cmp bs3d_room.pixels, 0 je bs_render_error diff --git a/src_graphics/card_table_input.inc b/src_graphics/card_table_input.inc index fd6fd25..764c5f3 100644 --- a/src_graphics/card_table_input.inc +++ b/src_graphics/card_table_input.inc @@ -129,6 +129,11 @@ LeaveCardTable3D PROC card_leave_hall: mov cardExitActive3D, 0 mov paused3D, 0 + cmp tournamentMode,1 + jne @F + call TowerExitToHall + ret +@@: mov engineMode, MODE_WALK call ClearKeys3D invoke PlayCasinoSound3D, 0, 0, 0 diff --git a/src_graphics/charms.inc b/src_graphics/charms.inc index a57d7b3..68cf7b9 100644 --- a/src_graphics/charms.inc +++ b/src_graphics/charms.inc @@ -1,129 +1,10 @@ +include src_graphics\artifact_system.inc -.code - -; ------------------------------------------------------------------------------ -; TriggerCharmLossProtection: Checks Devil's Contract and Clover to save bet -; Input: betAmount (DWORD) -; Output: eax = 1 if saved (bet refunded to bankroll), 0 if not saved -; ------------------------------------------------------------------------------ -TriggerCharmLossProtection PROC uses ebx ecx edx, betAmount:DWORD - cmp obsActive3D,0 - jne protection_none - ; 1. Check Devil's Contract (50% chance to refund full bet) - cmp charmDevilUses, 0 - jle check_clover - - invoke RandomRange, 100 - cmp eax, 50 - jge check_clover - - ; Devil Contract Triggered! - dec charmDevilUses - mov eax, betAmount - test eax, eax - jz @F - add bankroll, eax -@@: - invoke CopyString3D, OFFSET msgCharmDevilSave, OFFSET flashMessage - mov flashTimer, 120 - invoke PlayCasinoSound3D, OFFSET soundJackpot, 0, 1 - call SaveProgress3D - mov eax, 1 - ret - -check_clover: - ; 2. Check Clover of Luck (15% chance to refund bet) - cmp charmCloverUses, 0 - jle protection_none - - invoke RandomRange, 100 - cmp eax, 15 - jge protection_none - - ; Clover Triggered! - dec charmCloverUses - mov eax, betAmount - test eax, eax - jz @F - add bankroll, eax -@@: - invoke CopyString3D, OFFSET msgCharmCloverSave, OFFSET flashMessage - mov flashTimer, 120 - invoke PlayCasinoSound3D, OFFSET soundJackpot, 0, 1 - call SaveProgress3D - mov eax, 1 - ret - -protection_none: - xor eax, eax - ret -TriggerCharmLossProtection ENDP - -; ------------------------------------------------------------------------------ -; TriggerCharmWinBonus: Grants +15% extra payout on slot / roulette win -; Input: winAmount (DWORD) -; Output: eax = bonus amount added to bankroll, 0 if no horseshoe active -; ------------------------------------------------------------------------------ -TriggerCharmWinBonus PROC uses ebx ecx edx, winAmount:DWORD - cmp charmHorseshoeUses, 0 - jle bonus_none - - mov eax, winAmount - test eax, eax - jle bonus_none - - ; Calculate 15% bonus: (winAmount * 15) / 100 - imul eax, 15 - xor edx, edx - mov ecx, 100 - div ecx - cmp eax, 1 - jge @F - mov eax, 1 -@@: - mov ebx, eax - add bankroll, ebx - dec charmHorseshoeUses - invoke CopyString3D, OFFSET msgCharmHorseshoeWin, OFFSET flashMessage - mov flashTimer, 120 - invoke PlayCasinoSound3D, OFFSET soundCoin, 0, 1 - call SaveProgress3D - mov eax, ebx - ret - -bonus_none: - xor eax, eax - ret -TriggerCharmWinBonus ENDP - -; ------------------------------------------------------------------------------ -; TriggerCharmBulletProtection: Protects player from fatal gunshot -; Output: eax = 1 if saved (bullet misfired), 0 if no skull active -; ------------------------------------------------------------------------------ -TriggerCharmBulletProtection PROC uses ebx ecx edx - cmp charmSkullUses, 0 - jle bullet_none - - dec charmSkullUses - invoke CopyString3D, OFFSET msgCharmSkullSave, OFFSET flashMessage - mov flashTimer, 140 - invoke PlayCasinoSound3D, OFFSET rrDrySound3D, 0, 1 - call SaveProgress3D - mov eax, 1 - ret - -bullet_none: - xor eax, eax - ret -TriggerCharmBulletProtection ENDP - -; ------------------------------------------------------------------------------ -; DrawCharmsHUD: Draws active charm badges with remaining charges -; Input: drawX, drawY (screen coordinates) -; ------------------------------------------------------------------------------ DrawCharmsHUD PROC uses ebx esi edi, drawX:DWORD, drawY:DWORD local curX:DWORD, tempBuf[16]:BYTE + cmp tournamentMode,0 + jne hud_done mov eax, charmCloverUses or eax, charmHorseshoeUses or eax, charmSkullUses @@ -135,6 +16,8 @@ DrawCharmsHUD PROC uses ebx esi edi, drawX:DWORD, drawY:DWORD mov curX, eax ; 1. Clover Badge + test charmEnabledMask3D,1 + jz check_horse_hud cmp charmCloverUses, 0 jle check_horse_hud @@ -152,6 +35,8 @@ DrawCharmsHUD PROC uses ebx esi edi, drawX:DWORD, drawY:DWORD check_horse_hud: ; 2. Horseshoe Badge + test charmEnabledMask3D,2 + jz check_skull_hud cmp charmHorseshoeUses, 0 jle check_skull_hud @@ -169,6 +54,8 @@ check_horse_hud: check_skull_hud: ; 3. Skull Badge + test charmEnabledMask3D,4 + jz check_devil_hud cmp charmSkullUses, 0 jle check_devil_hud @@ -186,6 +73,8 @@ check_skull_hud: check_devil_hud: ; 4. Devil Contract Badge + test charmEnabledMask3D,8 + jz hud_done cmp charmDevilUses, 0 jle hud_done diff --git a/src_graphics/charms_data.inc b/src_graphics/charms_data.inc index 0f03943..3864ac8 100644 --- a/src_graphics/charms_data.inc +++ b/src_graphics/charms_data.inc @@ -1,19 +1,29 @@ CHARM_COUNT equ 4 +CHARM_MAX_USES equ 99 -CHARM_CLOVER_PRICE equ 12000 +CHARM_CLOVER_PRICE equ 900 CHARM_CLOVER_USES equ 3 -CHARM_HORSESHOE_PRICE equ 20000 +CHARM_HORSESHOE_PRICE equ 1800 CHARM_HORSESHOE_USES equ 5 -CHARM_SKULL_PRICE equ 35000 +CHARM_SKULL_PRICE equ 3500 CHARM_SKULL_USES equ 1 -CHARM_DEVIL_PRICE equ 50000 +CHARM_DEVIL_PRICE equ 4000 CHARM_DEVIL_USES equ 2 TriggerCharmLossProtection PROTO :DWORD TriggerCharmWinBonus PROTO :DWORD +PreviewArtifactSignature3D PROTO :DWORD +ArtifactSaveSignature3D PROTO :DWORD +ValidateArtifactSave3D PROTO :DWORD +ArtifactCredit3D PROTO :DWORD +ResetArtifacts3D PROTO +ClampArtifacts3D PROTO +ToggleArtifact3D PROTO :DWORD +RenderArtifactShop3D PROTO +HandleArtifactShop3D PROTO TriggerCharmBulletProtection PROTO DrawCharmsHUD PROTO :DWORD, :DWORD @@ -24,35 +34,57 @@ charmCloverUses dd 0 charmHorseshoeUses dd 0 charmSkullUses dd 0 charmDevilUses dd 0 +charmEnabledMask3D dd 7 +artifactBuyArmed3D dd 0 +artifactNum3D db 16 dup(0) ; Charm Store Metadata charmPrices3D dd CHARM_CLOVER_PRICE, CHARM_HORSESHOE_PRICE, CHARM_SKULL_PRICE, CHARM_DEVIL_PRICE charmUsesPack3D dd CHARM_CLOVER_USES, CHARM_HORSESHOE_USES, CHARM_SKULL_USES, CHARM_DEVIL_USES -charmNameClover db 'Êëåâåð Óäà÷è',0 -charmNameHorseshoe db 'Çîëîòàÿ Ïîäêîâà',0 -charmNameSkull db '×åðåï Áåññìåðòèÿ',0 -charmNameDevil db 'Êîíòðàêò Äüÿâîëà',0 - -charmNames3D dd OFFSET charmNameClover, OFFSET charmNameHorseshoe, OFFSET charmNameSkull, OFFSET charmNameDevil - -charmDescClover db '15% øàíñ ñïàñòè ñòàâêó',0 -charmDescHorseshoe db '+15% ê âûèãðûøó â ñëîòàõ',0 -charmDescSkull db '100% ñïàñåíèå îò ïóëè',0 -charmDescDevil db '50% øàíñ ñïàñòè ñòàâêó',0 - -charmDescs3D dd OFFSET charmDescClover, OFFSET charmDescHorseshoe, OFFSET charmDescSkull, OFFSET charmDescDevil - -; HUD Flash Messages -msgCharmCloverSave db 'Êëåâåð ñïàñ âàøó ñòàâêó!',0 -msgCharmDevilSave db 'Äüÿâîë: ñòàâêà âîçâðàùåíà!',0 -msgCharmHorseshoeWin db 'Ïîäêîâà: +15% ê âûèãðûøó!',0 -msgCharmSkullSave db '×åðåï: Îñå÷êà! Âû âûæèëè!',0 -msgCharmTowerSave db 'Òàëèñìàí ñïàñ âàñ îò ïàäåíèÿ ñ Áàøíè!',0 - -; Compact HUD Badges -badgeCloverPrefix db '[ÊË:',0 -badgeHorsePrefix db '[ÏÊ:',0 -badgeSkullPrefix db '[×Ð:',0 -badgeDevilPrefix db '[ÊÒ:',0 -badgeCloseBracket db '] ',0 +charmNameClover db 202,203,197,194,197,208,0 +charmNameHorseshoe db 207,206,196,202,206,194,192,0 +charmNameSkull db 215,197,208,197,207,0 +charmNameDevil db 202,206,205,210,208,192,202,210,0 +charmDescClover db 194,229,240,237,184,242,32,53,48,37,32,239,238,242,229,240,232,0 +charmDescHorseshoe db 43,51,53,37,32,247,232,241,242,238,233,32,239,240,232,225,251,235,232,0 +charmDescSkull db 209,239,224,241,184,242,32,238,242,32,241,236,229,240,242,232,0 +charmDescDevil db 211,228,226,238,232,242,32,239,240,232,225,251,235,252,0 +msgCharmCloverSave db 202,235,229,226,229,240,58,32,53,48,37,32,239,238,242,229,240,232,32,226,238,231,226,240,224,249,229,237,238,33,0 +msgCharmDevilSave db 202,238,237,242,240,224,234,242,58,32,225,238,237,243,241,32,234,32,239,240,232,225,251,235,232,33,0 +msgCharmHorseshoeWin db 207,238,228,234,238,226,224,58,32,43,51,53,37,32,247,232,241,242,238,233,32,239,240,232,225,251,235,232,33,0 +msgCharmSkullSave db 215,229,240,229,239,32,239,240,232,237,255,235,32,241,236,229,240,242,229,235,252,237,251,233,32,226,251,241,242,240,229,235,33,0 +msgCharmTowerSave db 208,229,235,232,234,226,232,255,58,32,229,249,184,32,238,228,237,224,32,239,238,239,251,242,234,224,33,0 +badgeCloverPrefix db 91,202,203,58,0 +badgeHorsePrefix db 91,207,196,58,0 +badgeSkullPrefix db 91,215,208,58,0 +badgeDevilPrefix db 91,196,202,58,0 +badgeCloseBracket db 93,32,0 +artifactShopTitle3D db 192,208,210,197,212,192,202,210,219,32,209,200,196,206,208,206,194,200,215,192,0 +artifactStock3D db 197,209,210,220,58,0 +artifactPack3D db 205,192,193,206,208,58,0 +artifactPrice3D db 214,197,205,192,58,0 +artifactOn3D db 194,202,203,0 +artifactOff3D db 194,219,202,203,0 +artifactToggle3D db 91,69,93,32,194,202,203,32,47,32,194,219,202,203,0 +artifactBuy3D db 91,69,78,84,69,82,93,32,202,211,207,200,210,220,0 +artifactConfirm3D db 91,69,78,84,69,82,93,32,207,206,196,210,194,197,208,196,200,210,220,0 +artifactBought3D db 205,192,193,206,208,32,202,211,207,203,197,205,0 +artifactLimit3D db 203,200,204,200,210,58,32,57,57,32,199,192,208,223,196,206,194,0 +artifactHelp3D db 84,65,66,58,32,208,211,193,192,216,202,200,32,32,69,58,32,221,212,212,197,202,210,32,32,69,83,67,58,32,213,206,203,203,0 +artifactTowerNote3D db 194,32,193,192,216,205,197,32,208,192,193,206,210,192,222,210,32,210,206,203,220,202,206,32,197,168,32,208,197,203,200,202,194,200,200,0 +artifactMode3D db 221,212,212,197,202,210,32,192,194,210,206,204,192,210,200,215,197,209,202,200,201,44,32,207,206,202,192,32,194,202,203,222,215,168,205,0 +artifactCloverRule3D db 196,238,32,49,48,48,48,48,32,231,224,32,239,240,238,232,227,240,224,237,237,251,233,32,240,224,243,237,228,0 +artifactHorseRule3D db 196,238,32,53,48,48,48,32,231,224,32,239,240,232,225,251,235,252,237,251,233,32,240,224,243,237,228,0 +artifactSkullRule3D db 208,243,241,241,234,224,255,32,240,243,235,229,242,234,224,32,232,32,66,117,99,107,115,104,111,116,0 +artifactDevilRule3D db 196,238,32,49,48,48,48,48,59,32,239,240,238,232,227,240,251,248,32,242,240,224,242,232,242,32,231,224,240,255,228,0 +artifactCloverUse3D db 199,224,240,255,228,32,231,224,32,226,238,231,226,240,224,242,0 +artifactHorseUse3D db 199,224,240,255,228,32,231,224,32,225,238,237,243,241,0 +artifactSkullUse3D db 199,224,240,255,228,32,231,224,32,241,239,224,241,229,237,232,229,0 +artifactDevilUse3D db 193,229,231,32,234,235,229,226,229,240,224,32,232,32,239,238,228,234,238,226,251,0 +charmNames3D dd OFFSET charmNameClover,OFFSET charmNameHorseshoe,OFFSET charmNameSkull,OFFSET charmNameDevil +charmDescs3D dd OFFSET charmDescClover,OFFSET charmDescHorseshoe,OFFSET charmDescSkull,OFFSET charmDescDevil +artifactRules3D dd OFFSET artifactCloverRule3D,OFFSET artifactHorseRule3D,OFFSET artifactSkullRule3D,OFFSET artifactDevilRule3D +artifactUseRules3D dd OFFSET artifactCloverUse3D,OFFSET artifactHorseUse3D,OFFSET artifactSkullUse3D,OFFSET artifactDevilUse3D +artifactColors3D dd 0088DD88h,00FFD070h,00CCDDEEh,00FF7777h +.code diff --git a/src_graphics/graphic_profiles.inc b/src_graphics/graphic_profiles.inc index 72c6a4f..6edd1d3 100644 --- a/src_graphics/graphic_profiles.inc +++ b/src_graphics/graphic_profiles.inc @@ -27,7 +27,7 @@ ValidateGraphicProfile3D PROC uses ebx esi edi path:DWORD jne profile_invalid cmp DWORD PTR entrySaveData3D[4], 1000000000 ja profile_invalid - cmp fileBytes, SAVE_FILE_SIZE + cmp fileBytes, SAVE_LEGACY_SIZE jb profile_valid mov esi, OFFSET entrySaveData3D mov ecx, SAVE_SIGN_SIZE/4 @@ -43,6 +43,13 @@ profile_signature: loop profile_signature cmp eax, DWORD PTR entrySaveData3D[SAVE_SIGN_SIZE] jne profile_invalid + cmp fileBytes,SAVE_LEGACY_SIZE + je profile_valid + cmp fileBytes,SAVE_FILE_SIZE + jne profile_invalid + invoke ValidateArtifactSave3D,OFFSET entrySaveData3D + test eax,eax + jz profile_invalid profile_valid: mov eax, 1 ret @@ -253,6 +260,7 @@ graphic_create_store: invoke CreateDirectoryA, OFFSET runtime3D, 0 invoke CreateDirectoryA, nativeProfilesDirPtr3D, 0 call ResetProfileDefaults + call ResetArtifacts3D invoke CopyCString, OFFSET saveFileName, OFFSET entryDisplayName3D mov nativeProfileActive3D, 1 call SyncGraphicBack3D diff --git a/src_graphics/hell_tower.inc b/src_graphics/hell_tower.inc index b793816..66778df 100644 --- a/src_graphics/hell_tower.inc +++ b/src_graphics/hell_tower.inc @@ -12,11 +12,13 @@ FreeTowerMinigameAssets ENDP InitHellTower3D PROC call FreeTowerMinigameAssets + call ResetTowerRelics3D mov tournamentMode, 1 mov towerStage, 1 mov towerFloorState, TOWER_STATE_BRIEFING mov towerAnimClock, 0 mov towerCalloutAnim, 0 + call PrepareTowerReward3D mov eax, towerFloorY[0] mov towerCursorY, eax @@ -33,6 +35,7 @@ InitHellTower3D ENDP TowerExitToHall PROC call StopTowerMusic call FreeTowerMinigameAssets + call ResetTowerRelics3D mov tournamentMode, 0 mov engineMode, MODE_WALK call ClearKeys3D @@ -40,6 +43,18 @@ TowerExitToHall PROC TowerExitToHall ENDP TowerLaunchCurrentFloor PROC uses ebx esi edi + cmp towerRewardPending3D,0 + jne tower_launch_blocked + cmp towerFloorState,TOWER_STATE_BRIEFING + jne tower_launch_blocked + cmp bankroll,100 + jae @F + invoke CopyString3D,OFFSET towerFundsLabel3D,OFFSET flashMessage + mov flashTimer,140 + ret +@@: + mov eax,bankroll + mov towerEntryBank3D,eax mov eax, towerStage dec eax cmp eax, 6 @@ -79,6 +94,7 @@ launch_valid: @@: cmp edx, MODE_ROULETTE jne @F + mov wheelChip3D,100 call InitNativeWheel3D mov engineMode, MODE_ROULETTE jmp launch_done @@ -86,16 +102,32 @@ launch_valid: cmp edx, MODE_BUCKSHOT jne launch_done call InitBuckshot3D + test eax,eax + jnz @F + mov towerFloorState,TOWER_STATE_BRIEFING + mov engineMode,MODE_HELL_TOWER + ret +@@: mov engineMode, MODE_BUCKSHOT launch_done: + ; Predictable entry stakes; all money remains in the shared profile bank. + mov coin3d_bet,50 + mov dice3d_bet,100 + mov cbBet3D,100 + mov rr3d_bet,100 + mov bs3d_bet,100 call ClearKeys3D ret +tower_launch_blocked: + ret TowerLaunchCurrentFloor ENDP TowerReportRoundOutcome PROC uses eax outcome:DWORD cmp tournamentMode, 1 jne report_exit + cmp towerFloorState,TOWER_STATE_IN_GAME + jne report_exit mov eax, outcome cmp eax, 1 jne report_loss @@ -105,6 +137,7 @@ TowerReportRoundOutcome PROC uses eax outcome:DWORD jae report_champion mov towerFloorState, TOWER_STATE_CLEAR + call PrepareTowerReward3D mov towerCalloutAnim, 0 mov eax, towerStage dec eax @@ -120,8 +153,8 @@ TowerReportRoundOutcome PROC uses eax outcome:DWORD report_champion: mov towerFloorState, TOWER_STATE_CHAMPION - mov eax, towerPrizeAmount - add bankroll, eax + invoke ArtifactCredit3D,towerPrizeAmount + call ResetTowerRelics3D invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 call SaveProgress3D mov engineMode, MODE_HELL_TOWER @@ -129,16 +162,39 @@ report_champion: ret report_loss: - invoke TriggerCharmLossProtection, 0 - test eax, eax - jz @F - mov towerFloorState, TOWER_STATE_BRIEFING - invoke CopyString3D, OFFSET msgCharmTowerSave, OFFSET flashMessage - mov flashTimer, 140 - mov engineMode, MODE_HELL_TOWER + cmp towerPurseUses3D,0 + je tower_loss_retry + mov eax,towerEntryBank3D + sub eax,bankroll + jbe tower_loss_retry + cmp eax,5000 + jbe @F + mov eax,5000 +@@: + invoke ArtifactCredit3D,eax + dec towerPurseUses3D + call SaveProgress3D +tower_loss_retry: + mov eax,towerStage + cmp eax,towerSealStage3D + jne tower_loss_heart + cmp towerSealUses3D,0 + je tower_loss_heart + dec towerSealUses3D + jmp tower_loss_saved +tower_loss_heart: + cmp towerHeartUses3D,0 + je tower_loss_final + dec towerHeartUses3D +tower_loss_saved: + mov towerFloorState,TOWER_STATE_BRIEFING + invoke CopyString3D,OFFSET towerRetryMessage3D,OFFSET flashMessage + mov flashTimer,140 + mov engineMode,MODE_HELL_TOWER call ClearKeys3D ret -@@: +tower_loss_final: + call ResetTowerRelics3D mov towerFloorState, TOWER_STATE_DEFEAT call StopTowerMusic invoke PlayCasinoSound3D, OFFSET soundRoundLose, 0, 1 @@ -151,6 +207,11 @@ report_exit: TowerReportRoundOutcome ENDP HandleHellTowerInput PROC + cmp towerRewardPending3D,1 + jne @F + call HandleTowerReward3D + ret +@@: mov eax, towerFloorState cmp eax, TOWER_STATE_BRIEFING je input_briefing @@ -240,6 +301,11 @@ HandleHellTowerInput ENDP RenderHellTower3D PROC uses ebx esi edi local cardX:DWORD, cardY:DWORD, cardW:DWORD, cardH:DWORD local currY:DWORD, tgtY:DWORD + cmp towerRewardPending3D,1 + jne @F + call RenderTowerReward3D + ret +@@: ; 1. Background: draw HellTower menu art if loaded, else fallback to maroon cmp hellTowerMenuArt3D.pixels, 0 @@ -456,10 +522,18 @@ render_card_briefing: invoke DrawBoxBorder, eax, 134, ecx, 32, 00180303h, 00661111h, 1 mov eax, cardX add eax, 12 - invoke DrawString8x8, eax, 139, OFFSET towerRuleText, 00FFAA33h + invoke DrawString8x8,eax,139,OFFSET towerHeartLabel3D,00FFAA33h + invoke FormatNumber,towerHeartUses3D,OFFSET towerRelicNum3D + mov eax,cardX + add eax,78 + invoke DrawString8x8,eax,139,OFFSET towerRelicNum3D,00FFFFFFh mov eax, cardX add eax, 12 - invoke DrawString8x8, eax, 151, OFFSET towerLossWarning, 00FF5555h + invoke DrawString8x8,eax,151,OFFSET towerSealLabel3D,00FFAA33h + invoke FormatNumber,towerSealUses3D,OFFSET towerRelicNum3D + mov eax,cardX + add eax,78 + invoke DrawString8x8,eax,151,OFFSET towerRelicNum3D,00FFFFFFh ; Bankroll & Prize (Y = 172) mov eax, cardX @@ -552,6 +626,7 @@ render_card_champion: invoke DrawString8x8, eax, 184, OFFSET towerReturnBtn, 00FFFFFFh render_tower_done: + call RenderTowerRelicStock3D ret RenderHellTower3D ENDP @@ -568,7 +643,10 @@ RenderTowerInGameBadge PROC invoke FormatNumber, towerStage, OFFSET hudNumBuffer invoke DrawString8x8, 64, 8, OFFSET hudNumBuffer, 00FFFFFFh invoke DrawString8x8, 72, 8, OFFSET towerHudOfTen, 00AAAAAAh + call RenderTowerRelicStock3D badge_done: ret RenderTowerInGameBadge ENDP + +include src_graphics\tower_relics.inc diff --git a/src_graphics/hell_tower_data.inc b/src_graphics/hell_tower_data.inc index 213e92a..8948d8c 100644 --- a/src_graphics/hell_tower_data.inc +++ b/src_graphics/hell_tower_data.inc @@ -82,3 +82,74 @@ towerPrizeText db 202,211,216,58,32,43,36,54,54,44,54,48,48,32,194,32,193,192,20 towerHudBadge db 193,192,216,205,223,58,32,0 towerHudOfTen db 47,54,0 + +RenderTowerRelicStock3D PROTO +ResetTowerRelics3D PROTO +PrepareTowerReward3D PROTO +ChooseTowerRelic3D PROTO :DWORD +HandleTowerReward3D PROTO +RenderTowerReward3D PROTO +TryTowerBustSave3D PROTO +UpdateTowerEye3D PROTO +.data +towerHeartUses3D dd 0 +towerSealUses3D dd 0 +towerSealStage3D dd 0 +towerBulletUses3D dd 0 +towerBustUses3D dd 0 +towerEye3D dd 0 +towerPurseUses3D dd 0 +towerRewardPending3D dd 0 +towerRewardSelection3D dd 0 +towerEntryBank3D dd 0 +towerRewardOffers3D dd 0,1,5 +towerRelicNum3D db 16 dup(0) +towerRewardTitle3D db 194,219,193,197,208,200,32,208,197,203,200,202,194,200,222,0 +towerRewardHint3D db 193,192,205,202,32,206,193,217,200,201,46,32,208,197,203,200,202,194,200,200,32,205,192,32,206,196,200,205,32,199,192,193,197,195,46,0 +towerRewardKeys3D db 49,32,47,32,50,32,47,32,51,58,32,194,219,193,206,208,32,32,32,69,78,84,69,82,58,32,194,199,223,210,220,0 +towerRunOnly3D db 205,192,32,199,192,193,197,195,0 +towerBulletMessage3D db 204,184,240,242,226,251,233,32,239,224,242,240,238,237,32,238,242,236,229,237,232,235,32,241,236,229,240,242,229,235,252,237,251,233,32,226,251,241,242,240,229,235,33,0 +towerBustMessage3D db 207,229,247,224,242,252,58,32,239,238,241,235,229,228,237,255,255,32,234,224,240,242,224,32,239,229,240,229,225,238,240,224,32,238,242,236,229,237,229,237,224,33,0 +towerRetryMessage3D db 208,229,235,232,234,226,232,255,32,241,239,224,241,235,224,32,231,224,225,229,227,46,32,207,238,226,242,238,240,232,32,253,242,224,230,33,0 +towerHeartLabel3D db 209,197,208,196,214,192,58,0 +towerSealLabel3D db 207,206,194,210,206,208,58,0 +towerBulletLabel3D db 199,192,217,200,210,192,58,0 +towerFundsLabel3D db 205,197,32,213,194,192,210,192,197,210,32,196,197,205,197,195,32,196,203,223,32,221,210,192,198,192,0 +towerRelicName0 db 209,197,208,196,214,197,0 +towerRelicDesc0 db 43,49,32,239,238,226,242,238,240,0 + db 239,240,232,32,239,240,238,226,224,235,229,0 + db 235,254,225,238,233,32,253,242,224,230,0 +towerRelicName1 db 215,192,209,219,0 +towerRelicDesc1 db 50,32,239,238,226,242,238,240,224,0 + db 239,240,232,32,239,240,238,226,224,235,229,0 + db 241,235,229,228,46,32,253,242,224,230,224,0 +towerRelicName2 db 207,192,210,208,206,205,0 +towerRelicDesc2 db 50,32,241,239,224,241,229,237,232,255,0 + db 238,242,32,241,236,229,240,242,232,0 + db 238,242,32,226,251,241,242,240,229,235,224,0 +towerRelicName3 db 207,197,215,192,210,220,0 +towerRelicDesc3 db 50,32,238,242,236,229,237,251,0 + db 239,238,241,235,229,228,237,229,233,0 + db 234,224,240,242,251,32,62,50,49,0 +towerRelicName4 db 206,202,206,32,207,192,203,192,215,192,0 +towerRelicDesc4 db 194,32,66,117,99,107,115,104,111,116,0 + db 226,232,228,229,237,32,241,235,229,228,46,0 + db 239,224,242,240,238,237,0 +towerRelicName5 db 202,206,216,197,203,220,0 +towerRelicDesc5 db 50,32,226,238,231,226,240,224,242,224,0 + db 231,224,32,239,240,238,226,224,235,0 + db 228,238,32,53,48,48,48,0 +towerRelicNames3D dd OFFSET towerRelicName0,OFFSET towerRelicName1,OFFSET towerRelicName2,OFFSET towerRelicName3,OFFSET towerRelicName4,OFFSET towerRelicName5 +towerRelicDescriptions3D dd OFFSET towerRelicDesc0,OFFSET towerRelicDesc1,OFFSET towerRelicDesc2,OFFSET towerRelicDesc3,OFFSET towerRelicDesc4,OFFSET towerRelicDesc5 +.code + +.data +towerStockLabel0 db 209,197,208,0 +towerStockLabel1 db 215,192,209,0 +towerStockLabel2 db 207,192,210,0 +towerStockLabel3 db 207,197,215,0 +towerStockLabel4 db 206,202,206,0 +towerStockLabel5 db 202,206,216,0 +towerStockLabels3D dd OFFSET towerStockLabel0,OFFSET towerStockLabel1,OFFSET towerStockLabel2,OFFSET towerStockLabel3,OFFSET towerStockLabel4,OFFSET towerStockLabel5 +towerStockValues3D dd OFFSET towerHeartUses3D,OFFSET towerSealUses3D,OFFSET towerBulletUses3D,OFFSET towerBustUses3D,OFFSET towerEye3D,OFFSET towerPurseUses3D +.code diff --git a/src_graphics/merchant_scene.inc b/src_graphics/merchant_scene.inc index 9548027..ccf4b3b 100644 --- a/src_graphics/merchant_scene.inc +++ b/src_graphics/merchant_scene.inc @@ -4,7 +4,8 @@ BeginMerchant3D PROC mov merchantTime3D, 0 mov merchantReaction3D, 1280 mov merchantMessage3D, 0 - mov merchantTab3D, 0 + mov merchantTab3D, 1 + mov artifactBuyArmed3D,0 mov merchantCharmSelected3D, 0 mov eax, selectedBack3D cmp eax, 12 @@ -48,49 +49,40 @@ merchant_purchase_done: ret BuyMerchantBack3D ENDP -BuyMerchantCharm3D PROC uses ebx charmIndex:DWORD - mov ebx, charmIndex - cmp ebx, 3 - ja charm_purchase_done - mov eax, charmPrices3D[ebx*4] - cmp bankroll, eax +BuyMerchantCharm3D PROC uses ebx esi charmIndex:DWORD + mov ebx,charmIndex + cmp ebx,CHARM_COUNT + jae charm_purchase_done + lea esi,charmCloverUses + mov eax,[esi+ebx*4] + add eax,charmUsesPack3D[ebx*4] + cmp eax,CHARM_MAX_USES + ja charm_purchase_limit + mov eax,charmPrices3D[ebx*4] + cmp bankroll,eax jb charm_purchase_poor - sub bankroll, eax - mov ecx, charmUsesPack3D[ebx*4] - cmp ebx, 0 - jne @F - add charmCloverUses, ecx - jmp charm_bought -@@: - cmp ebx, 1 - jne @F - add charmHorseshoeUses, ecx - jmp charm_bought -@@: - cmp ebx, 2 - jne @F - add charmSkullUses, ecx - jmp charm_bought -@@: - cmp ebx, 3 - jne charm_bought - add charmDevilUses, ecx -charm_bought: - mov merchantMessage3D, 1 - mov merchantReaction3D, 0 - mov merchantReactionKind3D, 2 + sub bankroll,eax + mov eax,charmUsesPack3D[ebx*4] + add [esi+ebx*4],eax + mov merchantMessage3D,1 call SaveProgress3D - invoke PlayCasinoSound3D, OFFSET soundCoin, 0, 1 + invoke PlayCasinoSound3D,OFFSET soundCoin,0,1 + jmp charm_purchase_done +charm_purchase_limit: + mov merchantMessage3D,3 ret charm_purchase_poor: - mov merchantMessage3D, 2 - mov merchantReaction3D, 0 - mov merchantReactionKind3D, 1 + mov merchantMessage3D,2 charm_purchase_done: ret BuyMerchantCharm3D ENDP HandleMerchant3D PROC uses ebx + cmp merchantTab3D,1 + jne @F + call HandleArtifactShop3D + ret +@@: mov eax, merchantSelected3D cmp eax, merchantPreviousFocus3D je @F @@ -256,6 +248,11 @@ HandleMerchant3D ENDP RenderMerchant3D PROC uses ebx esi edi local rowY:DWORD,start:DWORD,frame:DWORD,ink:DWORD + cmp merchantTab3D,1 + jne @F + call RenderArtifactShop3D + ret +@@: invoke DrawBox, 0, 0, 320, 240, 00252B24h invoke DrawBox, 0, 21, 320, 7, 00836638h invoke DrawBox, 0, 151, 320, 63, 003C3024h @@ -451,3 +448,5 @@ merchant_render_footer: invoke DrawString8x8, 8, 229, OFFSET merchantShopKeysWithTabs, 00C5C7B8h ret RenderMerchant3D ENDP + +include src_graphics\artifact_shop.inc diff --git a/src_graphics/native_blackjack.inc b/src_graphics/native_blackjack.inc index 96d6cba..acdd1e4 100644 --- a/src_graphics/native_blackjack.inc +++ b/src_graphics/native_blackjack.inc @@ -75,6 +75,8 @@ NativeBJSettle3D PROC result:DWORD add eax, edx bj_settle_credit: add bankroll, eax + sub eax,cbStake3D + invoke TriggerCharmWinBonus,eax invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 jmp bj_settle_save bj_settle_loss: @@ -230,6 +232,9 @@ bj_animate_hit: jne bj_begin_dealer jmp bj_begin_player bj_player_bust: + call TryTowerBustSave3D + test eax,eax + jnz bj_begin_dealer invoke FinishBJPlayer3D, 1 ret bj_animate_dealer: @@ -327,6 +332,13 @@ bj_ready_input: jnz bj_tourney_report ret bj_tourney_report: + cmp cbOutcome3D,2 + jne @F + mov towerFloorState,TOWER_STATE_BRIEFING + mov engineMode,MODE_HELL_TOWER + call ClearKeys3D + ret +@@: cmp cbOutcome3D, 1 je bj_tourney_loss invoke TowerReportRoundOutcome, 1 diff --git a/src_graphics/native_clover.inc b/src_graphics/native_clover.inc index db3f866..596d0f6 100644 --- a/src_graphics/native_clover.inc +++ b/src_graphics/native_clover.inc @@ -186,10 +186,14 @@ SettleNativeClover3D PROC jmp clp_settle_leave @@: invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1 - invoke TriggerCharmWinBonus, clpGross3D + mov eax,clpGross3D + sub eax,clpStake3D + invoke TriggerCharmWinBonus,eax jmp clp_settle_leave clp_settle_loss: - invoke TriggerCharmLossProtection, clpStake3D + mov eax,clpStake3D + sub eax,clpGross3D + invoke TriggerCharmLossProtection,eax test eax, eax jnz clp_settle_leave invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1 diff --git a/src_graphics/native_coin.inc b/src_graphics/native_coin.inc index 1f88295..4c9f32c 100644 --- a/src_graphics/native_coin.inc +++ b/src_graphics/native_coin.inc @@ -58,6 +58,9 @@ CoinEscape3D PROC mov paused3D,0 cmp tournamentMode, 1 jne @F + call CoinSettle3D + call CloseCoinDetail3D + mov coinExitQueued3D,0 call TowerExitToHall ret @@: @@ -96,6 +99,7 @@ CoinSettle3D PROC jbe @F mov bankroll,1000000000 @@: + invoke TriggerCharmWinBonus,coin3d_bet invoke PlayCasinoSound3D,OFFSET soundCoinDing,0,1 jmp coin_settle_save coin_settle_loss: diff --git a/src_graphics/native_dice.inc b/src_graphics/native_dice.inc index f1ab4bf..66249c9 100644 --- a/src_graphics/native_dice.inc +++ b/src_graphics/native_dice.inc @@ -35,6 +35,11 @@ DiceEscape3D PROC ret dice_escape_now: mov diceExitQueued3D,0 + cmp tournamentMode,1 + jne @F + call TowerExitToHall + ret +@@: mov engineMode,MODE_WALK call ClearKeys3D invoke PlayCasinoSound3D,0,0,0 diff --git a/src_graphics/native_durak.inc b/src_graphics/native_durak.inc index 8777efd..93a1b8a 100644 --- a/src_graphics/native_durak.inc +++ b/src_graphics/native_durak.inc @@ -127,6 +127,8 @@ NativeDurakSettle3D PROC result:DWORD shl eax, 1 @@: add bankroll, eax + sub eax,dkStake3D + invoke TriggerCharmWinBonus,eax invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 jmp dk_settle_save dk_settle_loss: diff --git a/src_graphics/native_poker.inc b/src_graphics/native_poker.inc index 4ffead1..3219d2d 100644 --- a/src_graphics/native_poker.inc +++ b/src_graphics/native_poker.inc @@ -255,6 +255,8 @@ PokerFold3D PROC actor:DWORD je poker_fold_player mov eax, pkPot3D add bankroll, eax + sub eax,pkPaid3D[0] + invoke TriggerCharmWinBonus,eax mov pkOutcome3D, 0 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 jmp poker_fold_settled @@ -343,6 +345,8 @@ poker_evaluate_player: je poker_showdown_tie mov eax, pkPot3D add bankroll, eax + sub eax,pkPaid3D[0] + invoke TriggerCharmWinBonus,eax mov pkOutcome3D, 0 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 jmp poker_showdown_settled diff --git a/src_graphics/native_slots.inc b/src_graphics/native_slots.inc index a90dc0c..ff3c84e 100644 --- a/src_graphics/native_slots.inc +++ b/src_graphics/native_slots.inc @@ -173,10 +173,14 @@ slot_settle_stops: jmp slot_settle_exit @@: invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1 - invoke TriggerCharmWinBonus, slotWinAmount + mov eax,slotWinAmount + sub eax,slotStake3D + invoke TriggerCharmWinBonus,eax jmp slot_settle_exit slot_settle_loss: - invoke TriggerCharmLossProtection, slotStake3D + mov eax,slotStake3D + sub eax,slotWinAmount + invoke TriggerCharmLossProtection,eax test eax, eax jnz slot_settle_exit invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1 diff --git a/src_graphics/native_wheel.inc b/src_graphics/native_wheel.inc index 1dd2522..f668a99 100644 --- a/src_graphics/native_wheel.inc +++ b/src_graphics/native_wheel.inc @@ -134,12 +134,15 @@ wheel_bank_cap: mov ecx, wheelReturn3D cmp ecx, wheelStake3D ja wheel_win_charm - cmp ecx, 0 - jne wheel_charm_done - invoke TriggerCharmLossProtection, wheelStake3D + je wheel_charm_done + mov eax,wheelStake3D + sub eax,wheelReturn3D + invoke TriggerCharmLossProtection,eax jmp wheel_charm_done wheel_win_charm: - invoke TriggerCharmWinBonus, wheelReturn3D + mov eax,wheelReturn3D + sub eax,wheelStake3D + invoke TriggerCharmWinBonus,eax wheel_charm_done: mov roulState, 2 xor eax,eax @@ -519,6 +522,11 @@ WheelEscape3D PROC ret wheel_leave_hall: mov wheelExitQueued3D,0 + cmp tournamentMode,1 + jne @F + call TowerExitToHall + ret +@@: mov engineMode,MODE_WALK call ClearKeys3D invoke PlayCasinoSound3D,0,0,0 diff --git a/src_graphics/platform_code.inc b/src_graphics/platform_code.inc index ad62451..9c5f2d3 100644 --- a/src_graphics/platform_code.inc +++ b/src_graphics/platform_code.inc @@ -549,14 +549,26 @@ LoadProgress3D PROC uses ebx mov charmHorseshoeUses, 0 mov charmSkullUses, 0 mov charmDevilUses, 0 + mov charmEnabledMask3D,7 invoke CreateFileA, OFFSET savePath3D, 80000000h, 1, 0, 3, 80h, 0 cmp eax, -1 je progress_loaded mov ebx, eax - invoke ReadFile, ebx, OFFSET saveData3D, 32, OFFSET saveIo3D, 0 + invoke ReadFile, ebx, OFFSET saveData3D, 40, OFFSET saveIo3D, 0 invoke CloseHandle, ebx cmp saveIo3D, 16 jb progress_loaded + cmp saveData3D,34443343h + jne progress_legacy_version + cmp saveIo3D,40 + jne progress_loaded + invoke PreviewArtifactSignature3D,OFFSET saveData3D + cmp eax,saveData3D[36] + jne progress_loaded + mov eax,saveData3D[32] + mov charmEnabledMask3D,eax + jmp progress_version_ok +progress_legacy_version: cmp saveData3D, 33443343h je progress_version_ok cmp saveData3D, 32443343h @@ -607,6 +619,7 @@ progress_version_ok: mov eax, saveData3D[28] mov charmDevilUses, eax progress_loaded: + call ClampArtifacts3D ret LoadProgress3D ENDP @@ -625,7 +638,7 @@ SaveProgress3D PROC uses ebx ret @@: invoke CreateDirectoryA, OFFSET runtime3D, 0 - mov saveData3D, 33443343h + mov saveData3D, 34443343h mov eax, bankroll mov saveData3D[4], eax mov eax, ownedBacks3D @@ -640,11 +653,15 @@ SaveProgress3D PROC uses ebx mov saveData3D[24], eax mov eax, charmDevilUses mov saveData3D[28], eax + mov eax,charmEnabledMask3D + mov saveData3D[32],eax + invoke PreviewArtifactSignature3D,OFFSET saveData3D + mov saveData3D[36],eax invoke CreateFileA, OFFSET savePath3D, 40000000h, 0, 0, 2, 80h, 0 cmp eax, -1 je progress_saved mov ebx, eax - invoke WriteFile, ebx, OFFSET saveData3D, 32, OFFSET saveIo3D, 0 + invoke WriteFile, ebx, OFFSET saveData3D, 40, OFFSET saveIo3D, 0 invoke CloseHandle, ebx progress_saved: ret diff --git a/src_graphics/platform_data.inc b/src_graphics/platform_data.inc index ce9d64b..20f8788 100644 --- a/src_graphics/platform_data.inc +++ b/src_graphics/platform_data.inc @@ -66,7 +66,7 @@ displaySettingsPath3D db "runtime\display.cfg",0 displaySettingsData3D dd 44535031h,1,0 displaySettingsIo3D dd 0 savePath3D db "runtime\casino3d-progress.dat",0 -saveData3D dd 32443343h, 10000, 0, 0, 0, 0, 0, 0 +saveData3D dd 32443343h,10000,0,0,0,0,0,0,7,0 saveIo3D dd 0 previewBackMap3D dd 0,3,5,2 textPause3D db 202,192,199,200,205,206,32,47,32,205,192,209,210,208,206,201,202,200,0 diff --git a/src_graphics/russian_party.inc b/src_graphics/russian_party.inc index 673381b..73ae0b5 100644 --- a/src_graphics/russian_party.inc +++ b/src_graphics/russian_party.inc @@ -81,6 +81,8 @@ RRPartyWin3D PROC imul eax,rrPartySize3D mov rr3d_payout,eax add bankroll,eax + sub eax,rrPartyStake3D + invoke TriggerCharmWinBonus,eax mov rr3d_state,RR_WIN invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,20003h invoke NpcRecordRound3D,6,1 @@ -93,6 +95,7 @@ RRPartyLose3D PROC cmp rrPartySettled3D,0 jne rr_party_lose_done mov rrPartySettled3D,1 + invoke TriggerCharmLossProtection,rrPartyStake3D mov rr3d_state,RR_GLITCH invoke NpcRecordRound3D,6,0 call SaveProgress3D diff --git a/src_graphics/russian_roulette.inc b/src_graphics/russian_roulette.inc index 3193e1d..0573132 100644 --- a/src_graphics/russian_roulette.inc +++ b/src_graphics/russian_roulette.inc @@ -185,12 +185,14 @@ rr_live_round: add bankroll, eax mov rr3d_state, RR_WIN mov rrPartySettled3D,1 + invoke TriggerCharmWinBonus,rr3d_bet invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 20003h invoke NpcRecordRound3D,6,1 call SaveProgress3D ret rr_player_lost: mov rrPartySettled3D,1 + invoke TriggerCharmLossProtection,rr3d_bet mov rr3d_state, RR_GLITCH invoke NpcRecordRound3D,6,0 call SaveProgress3D diff --git a/src_graphics/scratch.inc b/src_graphics/scratch.inc index d25ca7a..9b39834 100644 --- a/src_graphics/scratch.inc +++ b/src_graphics/scratch.inc @@ -413,12 +413,15 @@ FinishScratch3D PROC mov ecx, scrGross3D cmp ecx, scrStake3D ja scratch_charm_win - cmp ecx, 0 - jne @F - invoke TriggerCharmLossProtection, scrStake3D + je @F + mov eax,scrStake3D + sub eax,scrGross3D + invoke TriggerCharmLossProtection,eax jmp @F scratch_charm_win: - invoke TriggerCharmWinBonus, scrGross3D + mov eax,scrGross3D + sub eax,scrStake3D + invoke TriggerCharmWinBonus,eax @@: call ScratchStartPayout3D call SaveProgress3D diff --git a/src_graphics/tower_relics.inc b/src_graphics/tower_relics.inc new file mode 100644 index 0000000..952b0db --- /dev/null +++ b/src_graphics/tower_relics.inc @@ -0,0 +1,278 @@ +; Run-local relics. Never serialized; all stakes still use the profile bankroll. +ResetTowerRelics3D PROC + mov towerHeartUses3D,0 + mov towerSealUses3D,0 + mov towerSealStage3D,0 + mov towerBulletUses3D,0 + mov towerBustUses3D,0 + mov towerEye3D,0 + mov towerPurseUses3D,0 + mov towerRewardPending3D,0 + mov towerRewardSelection3D,0 + mov towerEntryBank3D,0 + mov towerRewardOffers3D,0 + mov towerRewardOffers3D[4],0 + mov towerRewardOffers3D[8],0 + ret +ResetTowerRelics3D ENDP + +PrepareTowerReward3D PROC + mov towerRewardPending3D,1 + mov towerRewardSelection3D,0 + mov towerRewardOffers3D,0 + mov towerRewardOffers3D[8],5 + mov eax,towerStage + cmp towerFloorState,TOWER_STATE_CLEAR + jne @F + inc eax +@@: + mov towerSealStage3D,eax + mov towerSealUses3D,0 + mov ecx,1 + cmp eax,3 + jne @F + mov ecx,3 +@@: + cmp eax,4 + jne @F + mov ecx,2 +@@: + cmp eax,6 + jne @F + mov ecx,4 +@@: + mov towerRewardOffers3D[4],ecx + call ClearKeys3D + mov mouseClick3D,0 + ret +PrepareTowerReward3D ENDP + +ChooseTowerRelic3D PROC index:DWORD + cmp towerRewardPending3D,1 + jne tower_choose_done + mov ecx,index + cmp ecx,2 + ja tower_choose_done + mov eax,towerRewardOffers3D[ecx*4] + cmp eax,0 + jne @F + inc towerHeartUses3D +@@: + cmp eax,1 + jne @F + mov towerSealUses3D,2 +@@: + cmp eax,2 + jne @F + add towerBulletUses3D,2 +@@: + cmp eax,3 + jne @F + add towerBustUses3D,2 +@@: + cmp eax,4 + jne @F + mov towerEye3D,1 +@@: + cmp eax,5 + jne @F + add towerPurseUses3D,2 +@@: + mov towerRewardPending3D,0 + call ClearKeys3D + mov mouseClick3D,0 +tower_choose_done: + ret +ChooseTowerRelic3D ENDP + +HandleTowerReward3D PROC + invoke Pressed3D,1Bh + test eax,eax + jz @F + call TowerExitToHall + ret +@@: + invoke Pressed3D,25h + test eax,eax + jz @F + cmp towerRewardSelection3D,0 + je @F + dec towerRewardSelection3D +@@: + invoke Pressed3D,27h + test eax,eax + jz @F + cmp towerRewardSelection3D,2 + jae @F + inc towerRewardSelection3D +@@: + cmp mouseClick3D,0 + je tower_reward_keys + mov mouseClick3D,0 + cmp mouseY3D,73 + jb tower_reward_keys + cmp mouseY3D,183 + jae tower_reward_keys + mov eax,mouseX3D + sub eax,8 + js tower_reward_keys + xor edx,edx + mov ecx,102 + div ecx + cmp eax,2 + ja tower_reward_keys + mov towerRewardSelection3D,eax + invoke ChooseTowerRelic3D,eax + ret +tower_reward_keys: + invoke Pressed3D,31h + test eax,eax + jz @F + invoke ChooseTowerRelic3D,0 + ret +@@: + invoke Pressed3D,32h + test eax,eax + jz @F + invoke ChooseTowerRelic3D,1 + ret +@@: + invoke Pressed3D,33h + test eax,eax + jz @F + invoke ChooseTowerRelic3D,2 + ret +@@: + invoke Pressed3D,0Dh + test eax,eax + jnz tower_reward_confirm + invoke Pressed3D,20h + test eax,eax + jz tower_reward_done +tower_reward_confirm: + invoke ChooseTowerRelic3D,towerRewardSelection3D +tower_reward_done: + ret +HandleTowerReward3D ENDP + +RenderTowerReward3D PROC uses ebx esi edi + local x:DWORD,y:DWORD + invoke DrawBox,0,0,320,240,000C080Bh + invoke DrawBoxBorder,4,24,312,192,0014090Dh,00D08030h,2 + invoke DrawString8x8,16,32,OFFSET towerRewardTitle3D,00FFD080h + invoke DrawString8x8,16,47,OFFSET towerRewardHint3D,00CCCCBBh + xor ebx,ebx + mov x,8 +tower_reward_card: + mov edx,00604430h + cmp ebx,towerRewardSelection3D + jne @F + mov edx,00FFCC66h +@@: + invoke DrawBoxBorder,x,73,100,110,002A1717h,edx,2 + mov esi,towerRewardOffers3D[ebx*4] + mov eax,x + add eax,4 + invoke DrawString8x8,eax,83,towerRelicNames3D[esi*4],00FFDD88h + mov edi,towerRelicDescriptions3D[esi*4] + mov y,105 + push ebx + mov ebx,3 +@@: + mov eax,x + add eax,4 + invoke DrawString8x8,eax,y,edi,00DDD3CCh +tower_reward_text_end: + cmp BYTE PTR [edi],0 + je tower_reward_text_next + inc edi + jmp tower_reward_text_end +tower_reward_text_next: + inc edi + add y,13 + dec ebx + jnz @B + pop ebx + mov eax,x + add eax,4 + invoke DrawString8x8,eax,161,OFFSET towerRunOnly3D,00AA9999h + add x,102 + inc ebx + cmp ebx,3 + jb tower_reward_card + invoke DrawString8x8,16,195,OFFSET towerRewardKeys3D,00FFDD88h + call RenderTowerRelicStock3D + ret +RenderTowerReward3D ENDP + +; Six compact counters remain visible between floors and during play. +RenderTowerRelicStock3D PROC uses ebx esi + local x:DWORD,y:DWORD,stepX:DWORD + mov x,4 + mov y,229 + mov stepX,52 + cmp engineMode,MODE_HELL_TOWER + je tower_stock_menu + ; Keep table action buttons at the bottom unobscured. + invoke DrawBox,100,4,216,26,0014090Dh + mov x,104 + mov y,7 + mov stepX,70 + jmp tower_stock_start +tower_stock_menu: + invoke DrawBox,0,226,320,14,0014090Dh +tower_stock_start: + xor ebx,ebx +tower_stock_counter: + invoke DrawString8x8,x,y,towerStockLabels3D[ebx*4],00CCAA88h + mov esi,towerStockValues3D[ebx*4] + invoke FormatNumber,DWORD PTR [esi],OFFSET towerRelicNum3D + mov eax,x + add eax,26 + invoke DrawString8x8,eax,y,OFFSET towerRelicNum3D,00FFFFFFh + mov eax,stepX + add x,eax + inc ebx + cmp ebx,3 + jne @F + cmp engineMode,MODE_HELL_TOWER + je @F + mov x,104 + add y,12 +@@: + cmp ebx,6 + jb tower_stock_counter + ret +RenderTowerRelicStock3D ENDP + +TryTowerBustSave3D PROC + cmp tournamentMode,1 + jne tower_bust_none + cmp towerBustUses3D,0 + je tower_bust_none + dec towerBustUses3D + dec cbPlayerCount3D + invoke NativeBJScore3D,OFFSET cbPlayer3D,cbPlayerCount3D + mov cbPlayerScore3D,eax + invoke CopyString3D,OFFSET towerBustMessage3D,OFFSET flashMessage + mov flashTimer,140 + mov eax,1 + ret +tower_bust_none: + xor eax,eax + ret +TryTowerBustSave3D ENDP + +UpdateTowerEye3D PROC + cmp tournamentMode,1 + jne tower_eye_done + cmp towerEye3D,1 + jne tower_eye_done + mov eax,bs3d_shellIndex + cmp eax,bs3d_shellCount + jae tower_eye_done + mov eax,bs3d_shells[eax*4] + mov bs3d_known,eax +tower_eye_done: + ret +UpdateTowerEye3D ENDP diff --git a/tools/tests/arrival_test.inc b/tools/tests/arrival_test.inc index e4bf5a1..d3cf26b 100644 --- a/tools/tests/arrival_test.inc +++ b/tools/tests/arrival_test.inc @@ -640,6 +640,7 @@ arrival_admit_sequence: jne arrival_test_fail mov bankroll, 35000 call BeginMerchant3D + mov merchantTab3D,0 mov merchantSelected3D, 5 mov merchantReaction3D, 1280 mov eax, bankroll diff --git a/tools/tests/artifacts_test.inc b/tools/tests/artifacts_test.inc new file mode 100644 index 0000000..a2c6f6b --- /dev/null +++ b/tools/tests/artifacts_test.inc @@ -0,0 +1,502 @@ +; Native regression: real production procedures, isolated output paths. +.data +artifactTestPath3D db "build\artifacts\profile.dat",0 +artifactTestDir3D db "build\artifacts",0 +artifactTestPreview3D db "build\artifacts\preview.dat",0 +artifactTestLegacy3D db "build\artifacts\legacy.dat",0 +artifactTestProfileName3D db "profile.dat",0 +artifactTestLegacyName3D db "legacy.dat",0 +artifactTestBmp3D db "build\artifacts\shop.bmp",0 +artifactTestRewardBmp3D db "build\artifacts\rewards.bmp",0 +artifactTestHeader3D db 42h,4Dh + dd 1228854 + dw 0,0 + dd 54,40,640,-480 + dw 1,32 + dd 0,1228800,0,0,0,0 +.code +ArtifactTestCapture3D PROC uses ebx path:DWORD + local io:DWORD + invoke CreateFileA,path,40000000h,0,0,2,80h,0 + mov ebx,eax + invoke WriteFile,ebx,OFFSET artifactTestHeader3D,54,ADDR io,0 + invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,ADDR io,0 + invoke CloseHandle,ebx + ret +ArtifactTestCapture3D ENDP + +RunArtifactTest3D PROC uses ebx esi edi + local savedBank:DWORD,oldPath[260]:BYTE,io:DWORD + mov obsActive3D,0 + mov tournamentMode,0 + mov nativeProfileActive3D,0 + mov arrivalConfirmed3D,0 + call ResetArtifacts3D + mov bankroll,10000 + invoke BuyMerchantCharm3D,0 + cmp bankroll,9100 + jne artifact_fail_1 + cmp charmCloverUses,3 + jne artifact_fail_1 + invoke TriggerCharmLossProtection,100 + cmp bankroll,9150 + jne artifact_fail_1 + cmp charmCloverUses,2 + jne artifact_fail_1 + invoke TriggerCharmLossProtection,0 + cmp charmCloverUses,2 + jne artifact_fail_1 + mov charmHorseshoeUses,5 + invoke TriggerCharmWinBonus,1000 + cmp eax,350 + jne artifact_fail_2 + cmp bankroll,9500 + jne artifact_fail_2 + mov charmDevilUses,2 + invoke ToggleArtifact3D,3 + cmp charmEnabledMask3D,12 + jne artifact_fail_2 + invoke TriggerCharmWinBonus,100 + cmp eax,100 + jne artifact_fail_2 + cmp charmDevilUses,1 + jne artifact_fail_2 + invoke TriggerCharmLossProtection,100 + cmp eax,0 + jne artifact_fail_2 + cmp charmDevilUses,0 + jne artifact_fail_2 + mov charmEnabledMask3D,7 + mov bankroll,999999999 + invoke TriggerCharmWinBonus,1000000000 + cmp eax,1 + jne artifact_fail_3 + cmp bankroll,1000000000 + jne artifact_fail_3 + mov eax,charmHorseshoeUses + mov ebx,eax + invoke TriggerCharmWinBonus,100 + cmp charmHorseshoeUses,ebx + jne artifact_fail_3 + mov charmSkullUses,1 + mov obsActive3D,1 + invoke TriggerCharmBulletProtection + test eax,eax + jnz artifact_fail_4 + cmp charmSkullUses,1 + jne artifact_fail_4 + mov obsActive3D,0 + mov tournamentMode,1 + invoke TriggerCharmLossProtection,100 + test eax,eax + jnz artifact_fail_4 + invoke TriggerCharmBulletProtection + test eax,eax + jnz artifact_fail_4 + mov towerBulletUses3D,1 + invoke TriggerCharmBulletProtection + cmp eax,1 + jne artifact_fail_4 + cmp charmSkullUses,1 + jne artifact_fail_4 + cmp towerBulletUses3D,0 + jne artifact_fail_4 + mov tournamentMode,0 + mov charmCloverUses,98 + mov bankroll,10000 + invoke BuyMerchantCharm3D,0 + cmp bankroll,10000 + jne artifact_fail_5 + cmp charmCloverUses,98 + jne artifact_fail_5 + mov charmCloverUses,0 + mov bankroll,0 + invoke BuyMerchantCharm3D,0 + cmp charmCloverUses,0 + jne artifact_fail_5 + + ; Reward cannot be claimed twice; reset starts a fresh run without touching bank. + mov bankroll,10000 + mov towerStage,1 + mov towerFloorState,TOWER_STATE_BRIEFING + call ResetTowerRelics3D + call PrepareTowerReward3D + invoke ChooseTowerRelic3D,0 + invoke ChooseTowerRelic3D,0 + cmp towerHeartUses3D,1 + jne artifact_fail_6 + mov tournamentMode,1 + mov towerFloorState,TOWER_STATE_IN_GAME + invoke TowerReportRoundOutcome,2 + cmp towerFloorState,TOWER_STATE_BRIEFING + jne artifact_fail_6 + cmp towerHeartUses3D,0 + jne artifact_fail_6 + ; Duplicate report cannot kill a saved attempt. + invoke TowerReportRoundOutcome,2 + cmp towerFloorState,TOWER_STATE_BRIEFING + jne artifact_fail_6 + mov towerFloorState,TOWER_STATE_IN_GAME + invoke TowerReportRoundOutcome,1 + cmp towerRewardPending3D,1 + jne artifact_fail_6 + invoke ChooseTowerRelic3D,1 + cmp towerSealStage3D,2 + jne artifact_fail_6 + cmp towerSealUses3D,2 + jne artifact_fail_6 + mov towerStage,2 + mov towerFloorState,TOWER_STATE_IN_GAME + invoke TowerReportRoundOutcome,2 + cmp towerSealUses3D,1 + jne artifact_fail_6 + cmp towerFloorState,TOWER_STATE_BRIEFING + jne artifact_fail_6 + call ResetTowerRelics3D + cmp towerSealUses3D,0 + jne artifact_fail_6 + cmp bankroll,10000 + jne artifact_fail_6 + ; Exact net loss refunded, not arbitrary prize or a second stake payout. + mov towerPurseUses3D,1 + mov towerHeartUses3D,1 + mov towerEntryBank3D,10000 + mov bankroll,9900 + mov towerFloorState,TOWER_STATE_IN_GAME + invoke TowerReportRoundOutcome,2 + cmp bankroll,10000 + jne artifact_fail_6 + cmp towerPurseUses3D,0 + jne artifact_fail_6 + mov towerStage,6 + mov towerFloorState,TOWER_STATE_IN_GAME + invoke TowerReportRoundOutcome,1 + mov eax,bankroll + mov savedBank,eax + invoke TowerReportRoundOutcome,1 + mov eax,savedBank + cmp bankroll,eax + jne artifact_fail_6 + cmp towerHeartUses3D,0 + jne artifact_fail_6 + + ; Bust protection removes the real last card, without drawing a replacement. + mov towerBustUses3D,1 + mov cbPlayerCount3D,3 + mov cbPlayer3D,8 + mov cbPlayer3D[4],7 + mov cbPlayer3D[8],6 + call TryTowerBustSave3D + cmp eax,1 + jne artifact_fail_7 + cmp cbPlayerCount3D,2 + jne artifact_fail_7 + cmp cbPlayerScore3D,19 + jne artifact_fail_7 + mov towerEye3D,1 + mov bs3d_shellIndex,0 + mov bs3d_shellCount,2 + mov bs3d_shells,1 + call UpdateTowerEye3D + cmp bs3d_known,1 + jne artifact_fail_7 + mov tournamentMode,0 + call ResetTowerRelics3D + + ; Both legacy checksum and versioned extension must validate. + mov DWORD PTR [saveBuffer+280],32465241h + mov DWORD PTR [saveBuffer+284],3 + mov DWORD PTR [saveBuffer+288],5 + mov DWORD PTR [saveBuffer+292],1 + mov DWORD PTR [saveBuffer+296],2 + mov DWORD PTR [saveBuffer+300],7 + mov DWORD PTR [saveBuffer+304],2 + invoke ArtifactSaveSignature3D,OFFSET saveBuffer + mov DWORD PTR [saveBuffer+308],eax + invoke ValidateArtifactSave3D,OFFSET saveBuffer + cmp eax,1 + jne artifact_fail_8 + inc DWORD PTR [saveBuffer+284] + invoke ValidateArtifactSave3D,OFFSET saveBuffer + test eax,eax + jnz artifact_fail_8 + + ; Real profile write/reload uses test paths, never runtime/profiles. + invoke CreateDirectoryA,OFFSET artifactTestDir3D,0 + mov nativeProfilesDirPtr3D,OFFSET artifactTestDir3D + mov nativeProfileActive3D,1 + invoke CopyCString,OFFSET saveFileName,OFFSET artifactTestProfileName3D + mov charmCloverUses,3 + mov charmHorseshoeUses,5 + mov charmSkullUses,1 + mov charmDevilUses,2 + mov charmEnabledMask3D,7 + mov bankroll,12345 + call SaveGraphicProfile3D + invoke ValidateGraphicProfile3D,OFFSET artifactTestPath3D + cmp eax,1 + jne artifact_fail_9 + call ResetArtifacts3D + mov bankroll,0 + call LoadSave + cmp bankroll,12345 + jne artifact_fail_9 + cmp charmCloverUses,3 + jne artifact_fail_9 + cmp charmDevilUses,2 + jne artifact_fail_9 + cmp charmEnabledMask3D,7 + jne artifact_fail_9 + ; A signed 280-byte legacy file imports with an empty artifact inventory. + invoke CreateFileA,OFFSET artifactTestLegacy3D,40000000h,0,0,2,80h,0 + mov ebx,eax + invoke WriteFile,ebx,OFFSET saveBuffer,SAVE_LEGACY_SIZE,ADDR io,0 + invoke CloseHandle,ebx + invoke ValidateGraphicProfile3D,OFFSET artifactTestLegacy3D + cmp eax,1 + jne artifact_fail_9 + invoke CopyCString,OFFSET saveFileName,OFFSET artifactTestLegacyName3D + call LoadSave + cmp charmCloverUses,0 + jne artifact_fail_9 + cmp bankroll,12345 + jne artifact_fail_9 + + ; Preview format roundtrip, disabled effects and checksum rejection. + mov nativeProfileActive3D,0 + invoke CopyCString,OFFSET savePath3D,OFFSET artifactTestPreview3D + mov arrivalConfirmed3D,1 + mov bankroll,54321 + mov charmCloverUses,9 + mov charmHorseshoeUses,8 + mov charmSkullUses,7 + mov charmDevilUses,6 + mov charmEnabledMask3D,4 + call SaveProgress3D + mov arrivalConfirmed3D,0 + call ResetArtifacts3D + call LoadProgress3D + cmp bankroll,54321 + jne artifact_fail_10 + cmp charmCloverUses,9 + jne artifact_fail_10 + cmp charmDevilUses,6 + jne artifact_fail_10 + cmp charmEnabledMask3D,4 + jne artifact_fail_10 + invoke TriggerCharmWinBonus,100 + test eax,eax + jnz artifact_fail_10 + cmp charmHorseshoeUses,8 + jne artifact_fail_10 + inc saveData3D[16] + invoke CreateFileA,OFFSET artifactTestPreview3D,40000000h,0,0,2,80h,0 + mov ebx,eax + invoke WriteFile,ebx,OFFSET saveData3D,40,ADDR io,0 + invoke CloseHandle,ebx + call LoadProgress3D + cmp bankroll,10000 + jne artifact_fail_10 + cmp charmCloverUses,0 + jne artifact_fail_10 + call ResetProfileDefaults + cmp charmSkullUses,0 + jne artifact_fail_10 + + ; Production Buckshot settlement pays net-profit bonus and cannot repeat it. + mov bankroll,9900 + mov charmEnabledMask3D,7 + mov charmHorseshoeUses,2 + mov bs3d_bet,100 + mov bs3d_state,BS_SHOT + mov bs3d_afterAnim,BS_WIN + call FinishBuckshotAction3D + cmp bankroll,10270 + jne artifact_fail_11 + cmp charmHorseshoeUses,1 + jne artifact_fail_11 + call FinishBuckshotAction3D + cmp bankroll,10270 + jne artifact_fail_11 + mov bankroll,9900 + mov charmCloverUses,2 + mov bs3d_state,BS_SHOT + mov bs3d_afterAnim,BS_LOSE + call FinishBuckshotAction3D + cmp bankroll,9950 + jne artifact_fail_11 + cmp charmCloverUses,1 + jne artifact_fail_11 + + ; Keyboard purchase requires two presses; selection cancels confirmation. + mov bankroll,10000 + call ResetArtifacts3D + mov merchantTab3D,1 + mov merchantCharmSelected3D,0 + mov artifactBuyArmed3D,0 + call ClearKeys3D + mov pressedKeys3D[0Dh],1 + call HandleArtifactShop3D + cmp bankroll,10000 + jne artifact_fail_12 + cmp artifactBuyArmed3D,1 + jne artifact_fail_12 + mov pressedKeys3D[0Dh],1 + call HandleArtifactShop3D + cmp bankroll,9100 + jne artifact_fail_12 + cmp charmCloverUses,3 + jne artifact_fail_12 + mov artifactBuyArmed3D,1 + mov pressedKeys3D[28h],1 + call HandleArtifactShop3D + cmp artifactBuyArmed3D,0 + jne artifact_fail_12 + cmp merchantCharmSelected3D,1 + jne artifact_fail_12 + mov pressedKeys3D[09h],1 + call HandleArtifactShop3D + cmp merchantTab3D,0 + jne artifact_fail_12 + + ; Fresh run clears every relic, stale offer and entry bankroll. + mov towerHeartUses3D,5 + mov towerSealUses3D,2 + mov towerSealStage3D,6 + mov towerBulletUses3D,2 + mov towerBustUses3D,2 + mov towerEye3D,1 + mov towerPurseUses3D,2 + call ResetTowerRelics3D + mov eax,towerHeartUses3D + or eax,towerSealUses3D + or eax,towerSealStage3D + or eax,towerBulletUses3D + or eax,towerBustUses3D + or eax,towerEye3D + or eax,towerPurseUses3D + or eax,towerEntryBank3D + or eax,towerRewardPending3D + test eax,eax + jnz artifact_fail_12 + + ; Leaving each native table must end the run, not leak tournament mode to hall. + mov tournamentMode,1 + mov towerHeartUses3D,1 + mov dice3d_state,0 + call DiceEscape3D + cmp tournamentMode,0 + jne artifact_fail_13 + cmp towerHeartUses3D,0 + jne artifact_fail_13 + mov tournamentMode,1 + mov towerEye3D,1 + mov roulState,0 + call WheelEscape3D + cmp tournamentMode,0 + jne artifact_fail_13 + cmp towerEye3D,0 + jne artifact_fail_13 + mov tournamentMode,1 + mov towerBustUses3D,1 + mov engineMode,MODE_BLACKJACK + mov cbState3D,CB_IDLE + call LeaveCardTable3D + cmp tournamentMode,0 + jne artifact_fail_13 + cmp towerBustUses3D,0 + jne artifact_fail_13 + mov tournamentMode,1 + mov towerPurseUses3D,1 + mov bankroll,9950 + mov coin3d_bet,50 + mov coin3d_state,COIN_TOSS + mov coin3d_choice,1 + mov coin3d_result,1 + call CoinEscape3D + cmp bankroll,10050 + jne artifact_fail_13 + cmp tournamentMode,0 + jne artifact_fail_13 + cmp towerPurseUses3D,0 + jne artifact_fail_13 + call CoinSettle3D + cmp bankroll,10050 + jne artifact_fail_13 + + ; Situational offers follow the upcoming floor, not the one just cleared. + mov towerStage,2 + mov towerFloorState,TOWER_STATE_CLEAR + call PrepareTowerReward3D + cmp towerRewardOffers3D[4],3 + jne artifact_fail_14 + mov towerStage,3 + call PrepareTowerReward3D + cmp towerRewardOffers3D[4],2 + jne artifact_fail_14 + mov towerStage,5 + call PrepareTowerReward3D + cmp towerRewardOffers3D[4],4 + jne artifact_fail_14 + invoke ChooseTowerRelic3D,1 + cmp towerEye3D,1 + jne artifact_fail_14 + call ResetTowerRelics3D + + ; Render shipping UI; captures inspected by the Python runner. + mov merchantTab3D,1 + mov merchantCharmSelected3D,0 + mov merchantMessage3D,0 + call RenderMerchant3D + invoke ArtifactTestCapture3D,OFFSET artifactTestBmp3D + mov towerStage,3 + mov engineMode,MODE_HELL_TOWER + mov towerFloorState,TOWER_STATE_BRIEFING + call PrepareTowerReward3D + call RenderHellTower3D + invoke ArtifactTestCapture3D,OFFSET artifactTestRewardBmp3D + xor eax,eax + ret +artifact_fail_1: + mov eax,101 + ret +artifact_fail_2: + mov eax,102 + ret +artifact_fail_3: + mov eax,103 + ret +artifact_fail_4: + mov eax,104 + ret +artifact_fail_5: + mov eax,105 + ret +artifact_fail_6: + mov eax,106 + ret +artifact_fail_7: + mov eax,107 + ret +artifact_fail_8: + mov eax,108 + ret +artifact_fail_9: + mov eax,109 + ret +artifact_fail_10: + mov eax,110 + ret +artifact_fail_11: + mov eax,111 + ret +artifact_fail_12: + mov eax,112 + ret +artifact_fail_13: + mov eax,113 + ret +artifact_fail_14: + mov eax,114 + ret +RunArtifactTest3D ENDP diff --git a/tools/tests/check_artifacts.py b/tools/tests/check_artifacts.py new file mode 100644 index 0000000..0b27c1e --- /dev/null +++ b/tools/tests/check_artifacts.py @@ -0,0 +1,95 @@ +"""Compile shipping ASM procedures with isolated artifact/tower regression entry.""" +import hashlib +import json +import re +from pathlib import Path +import struct +import subprocess +from PIL import Image + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT / 'build/artifacts' + + +def toolchain(): + for base in (Path('C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC'), + Path('C:/Program Files (x86)/Microsoft Visual Studio/18/BuildTools/VC/Tools/MSVC')): + if base.exists(): + for version in sorted(base.iterdir(), reverse=True): + binary = version / 'bin/Hostx64/x86' + if (binary / 'ml.exe').exists(): + return binary / 'ml.exe', binary / 'link.exe' + for base in (Path('F:/MASM32/masm32'), Path('D:/MASM32/masm32'), Path('C:/masm32')): + if (base/'bin/ml.exe').exists(): + return base/'bin/ml.exe', base/'bin/link.exe' + raise RuntimeError('MASM x86 toolchain required') + + +def run(*args): + subprocess.run([str(x) for x in args], cwd=ROOT, check=True, timeout=120) + + +def check_text_widths(): + source = (ROOT/'src_graphics/hell_tower_data.inc').read_text(encoding='cp1251') + for index in range(6): + block = source.split(f'towerRelicName{index} db ', 1)[1].split('\ntowerRelicName', 1)[0] + block = block.split('\ntowerRelicNames3D', 1)[0] + rows = re.findall(r'(?:^|\bdb )([\d,]+)', block) + assert len(rows) == 4, (index, rows) + for row in rows: + data = bytes(map(int, row.split(','))).rstrip(b'\0') + assert len(data) <= 11, (index, data.decode('cp1251')) + assert 151 not in data + for label, width in [('towerRewardTitle3D', 36), ('towerRewardHint3D', 36), + ('towerRewardKeys3D', 36), ('towerRunOnly3D', 11)]: + row = re.search(rf'^{label} db ([\d,]+)', source, re.M).group(1) + data = bytes(map(int, row.split(','))).rstrip(b'\0') + assert len(data) <= width and 151 not in data, label + + +def main(): + check_text_widths() + OUT.mkdir(parents=True, exist_ok=True) + runtime = ROOT/'runtime' + before = {p: hashlib.sha256(p.read_bytes()).digest() for p in runtime.rglob('*') if p.is_file()} + ml, link = toolchain() + sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib') + libraries = next(p/'um/x86' for p in sorted(sdk.iterdir(), reverse=True) + if (p/'um/x86/kernel32.lib').exists()) + run(ml, '/c', '/coff', '/Cp', '/nologo', '/I.', '/DARTIFACT_TEST=1', + '/Fobuild/artifacts/test.obj', 'poker.asm') + run(link, '/nologo', '/SUBSYSTEM:CONSOLE', '/LIBPATH:'+str(libraries), + '/OUT:build/artifacts/test.exe', 'build/artifacts/test.obj', + 'kernel32.lib', 'winmm.lib', 'user32.lib', 'gdi32.lib', 'opengl32.lib') + run(OUT/'test.exe') + assert all(p.exists() and hashlib.sha256(p.read_bytes()).digest() == h for p,h in before.items()) + assert {p for p in runtime.rglob('*') if p.is_file()} == set(before), 'test wrote player runtime' + profile = (OUT/'profile.dat').read_bytes() + assert len(profile) == 312 + assert struct.unpack_from('<4s6I', profile, 280)[:6] == (b'ARF2',3,5,1,2,7) + assert len((OUT/'legacy.dat').read_bytes()) == 280 + for name in ('shop', 'rewards'): + image = Image.open(OUT/f'{name}.bmp').convert('RGB') + assert image.size == (640,480) and len(image.getcolors(640*480)) > 5 + image.save(OUT/f'{name}.png') + if name == 'rewards': + # Reward scene must not retain any pixels from previously rendered shop. + assert image.getpixel((0,0)) == (12,8,11) + assert image.getpixel((639,449)) == (12,8,11) + assert image.getpixel((639,479)) == (20,9,13) + report = {'native': 'PASS', 'profile_bytes': len(profile), + 'player_runtime_unchanged': True, + 'checks': ['purchases', 'caps', 'toggles', 'observer isolation', 'tower isolation', + 'retries', 'reward idempotence', 'prize idempotence', 'bust save', + 'shell reveal', 'checksums', 'profile roundtrip', 'legacy import', + 'preview roundtrip and tamper rejection', 'disabled effects', + 'Buckshot net settlement and idempotence', 'purchase confirmation', + 'selection cancels confirmation', 'fresh run full reset', + 'native table exits reset tower', 'paid coin exit settles once', + 'text widths', 'UI captures']} + (OUT/'report.json').write_text(json.dumps(report, indent=2), encoding='utf-8') + print('PASS: native artifacts, run-local relics, profile migration and UI captures') + + +if __name__ == '__main__': + main() diff --git a/tools/tests/check_casino_actions.py b/tools/tests/check_casino_actions.py new file mode 100644 index 0000000..21a3c7a --- /dev/null +++ b/tools/tests/check_casino_actions.py @@ -0,0 +1,30 @@ +"""Build full native action suite without changing production source or executables.""" +from pathlib import Path +import subprocess +import sys +from check_artifacts import toolchain + +ROOT = Path(__file__).resolve().parents[2] + + +def main(): + (ROOT/'build/actions').mkdir(parents=True, exist_ok=True) + for fixture in ('prepare_arrival_fixtures.py', 'prepare_clover_fixtures.py', + 'prepare_poker_fixtures.py'): + subprocess.run([sys.executable, '-B', str(ROOT/'tools/tests'/fixture)], check=True, cwd=ROOT) + ml, link = toolchain() + sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib') + libraries = next(p/'um/x86' for p in sorted(sdk.iterdir(), reverse=True) + if (p/'um/x86/kernel32.lib').exists()) + subprocess.run([str(ml), '/c', '/coff', '/Cp', '/nologo', '/I.', '/DCASINO_ACTION_TEST=1', + '/Fobuild/actions/actions-test.obj', 'poker.asm'], cwd=ROOT, check=True) + subprocess.run([str(link), '/nologo', '/SUBSYSTEM:CONSOLE', '/LIBPATH:'+str(libraries), + '/OUT:build/actions/actions-test.exe', 'build/actions/actions-test.obj', + 'kernel32.lib', 'winmm.lib', 'user32.lib', 'gdi32.lib', 'opengl32.lib'], + cwd=ROOT, check=True) + subprocess.run([str(ROOT/'build/actions/actions-test.exe')], cwd=ROOT, check=True, timeout=600) + print((ROOT/'build/actions/test-report.txt').read_text(encoding='ascii')) + + +if __name__ == '__main__': + main() diff --git a/tools/tests/check_hall_expansion.py b/tools/tests/check_hall_expansion.py index 1b1c64a..b23eed9 100644 --- a/tools/tests/check_hall_expansion.py +++ b/tools/tests/check_hall_expansion.py @@ -9,6 +9,7 @@ from PIL import Image, ImageStat, ImageChops from check_card_exit_build import Coff from check_terminals import signature from check_character_cast import check as check_character_cast +from check_artifacts import toolchain ROOT = Path(__file__).resolve().parents[2] OUT = ROOT / "build/hall-expansion" @@ -329,11 +330,11 @@ def main(): (OUT/(stem+".asm")).write_text(source,encoding="cp1251") masm = Path("F:/MASM32/masm32") modern_ml = Path("C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.51.36231/bin/Hostx64/x86/ml.exe") - assembler = modern_ml if modern_ml.is_file() else masm/"bin/ml.exe" + assembler, linker = toolchain() subprocess.run([str(assembler),"/c","/coff","/Cp","/nologo","/I.", f"/Fobuild/hall-expansion/{stem}.obj",f"build/hall-expansion/{stem}.asm"],cwd=ROOT,check=True) sdk = sorted(Path("C:/Program Files (x86)/Windows Kits/10/Lib").glob("*/um/x86"))[-1] - subprocess.run([str(masm/"bin/link.exe"),"/nologo","/SUBSYSTEM:CONSOLE","/ENTRY:ExpansionTestStart@0", + subprocess.run([str(linker),"/nologo","/SUBSYSTEM:CONSOLE","/ENTRY:ExpansionTestStart@0", f"/MAP:build/hall-expansion/{stem}.map", f"/LIBPATH:{sdk}",f"/LIBPATH:{masm/'lib'}",f"/OUT:build/hall-expansion/{stem}.exe", f"build/hall-expansion/{stem}.obj","build/hall-expansion/helpers.obj", diff --git a/tools/tests/prepare_poker_fixtures.py b/tools/tests/prepare_poker_fixtures.py new file mode 100644 index 0000000..b40dd40 --- /dev/null +++ b/tools/tests/prepare_poker_fixtures.py @@ -0,0 +1,30 @@ +"""Generate independent evaluator vectors only; never rewrite shipping assets.""" +import itertools +from pathlib import Path +import random +import struct +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT/'tools/vendor')) +from treys import Card, Evaluator + + +def main(): + rng = random.Random(77291) + hands = [list(hand) for hand in itertools.combinations([8,9,10,11,12,13,25], 5)] + hands += [rng.sample(range(52), count) for count in (5,6,7) for _ in range(3000)] + evaluator = Evaluator() + def native(card): + return Card.new('23456789TJQKA'[card%13]+'cdhs'[card//13]) + vectors = b''.join(struct.pack('<9I', len(hand), *hand, *([0]*(7-len(hand))), + evaluator.evaluate([], [native(card) for card in hand])) + for hand in hands) + target = ROOT/'build/tables/poker-vectors.bin' + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(struct.pack('=10 +capstone>=5,<6 +treys==0.1.8 diff --git a/tools/tests/test_hell_tower_runthrough.py b/tools/tests/test_hell_tower_runthrough.py index ee1d13b..7fee00b 100644 --- a/tools/tests/test_hell_tower_runthrough.py +++ b/tools/tests/test_hell_tower_runthrough.py @@ -1,45 +1,34 @@ -"""Automated smoke test for Hell Tower 6-floor run-through.""" -import subprocess -import sys +"""Smoke-test six real tower scene launches; outcomes injected, not balance measurement.""" +import hashlib from pathlib import Path +import subprocess +from check_artifacts import toolchain ROOT = Path(__file__).resolve().parents[2] -POKER_ASM = ROOT / "poker.asm" +OUT = ROOT/'build/hell-tower' def main(): - print("[1/4] Preparing test harness in poker.asm...") - code = POKER_ASM.read_text(encoding="cp1251") - target = "\nIFDEF CASINO_ACTION_TEST\n" - if "CASINO_ACTION_TEST equ 1\n" not in code: - code = code.replace(target, "\nCASINO_ACTION_TEST equ 1\nIFDEF CASINO_ACTION_TEST\n") - POKER_ASM.write_text(code, encoding="cp1251") - - try: - print("[2/4] Building poker.exe with test harness...") - build_res = subprocess.run(["cmd.exe", "/c", "build.bat"], cwd=ROOT, capture_output=True, text=True) - if build_res.returncode != 0: - print("[FAIL] Build failed:", build_res.stdout, build_res.stderr) - return 1 - - print("[3/4] Running Hell Tower 6-floor run-through...") - run_res = subprocess.run([str(ROOT / "poker.exe")], cwd=ROOT, capture_output=True, timeout=15) - print(f"Hell Tower test completed with exit code: {run_res.returncode}") - if run_res.returncode != 0: - print(f"[FAIL] Hell Tower run-through failed with code {run_res.returncode}") - return run_res.returncode - print("[SUCCESS] All 6 floors of Hell Tower completed without errors or crashes!") - finally: - print("[4/4] Restoring normal poker.asm and rebuilding...") - restored = POKER_ASM.read_text(encoding="cp1251") - if "CASINO_ACTION_TEST equ 1\n" in restored: - restored = restored.replace("CASINO_ACTION_TEST equ 1\n", "") - POKER_ASM.write_text(restored, encoding="cp1251") - subprocess.run(["cmd.exe", "/c", "build.bat"], cwd=ROOT, capture_output=True, text=True) - print("[OK] Production poker.exe rebuilt cleanly.") - - return 0 + OUT.mkdir(parents=True, exist_ok=True) + source_hash = hashlib.sha256((ROOT/'poker.asm').read_bytes()).digest() + runtime = {p: hashlib.sha256(p.read_bytes()).digest() + for p in (ROOT/'runtime').rglob('*') if p.is_file()} + ml, link = toolchain() + sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib') + libraries = next(p/'um/x86' for p in sorted(sdk.iterdir(), reverse=True) + if (p/'um/x86/kernel32.lib').exists()) + subprocess.run([str(ml), '/c', '/coff', '/Cp', '/nologo', '/I.', '/DHELL_TOWER_TEST=1', + '/Fobuild/hell-tower/test.obj', 'poker.asm'], cwd=ROOT, check=True) + subprocess.run([str(link), '/nologo', '/SUBSYSTEM:CONSOLE', '/LIBPATH:'+str(libraries), + '/OUT:build/hell-tower/test.exe', 'build/hell-tower/test.obj', + 'kernel32.lib', 'winmm.lib', 'user32.lib', 'gdi32.lib', 'opengl32.lib'], + cwd=ROOT, check=True) + subprocess.run([str(OUT/'test.exe')], cwd=ROOT, check=True, timeout=90) + assert hashlib.sha256((ROOT/'poker.asm').read_bytes()).digest() == source_hash + assert {p: hashlib.sha256(p.read_bytes()).digest() + for p in (ROOT/'runtime').rglob('*') if p.is_file()} == runtime + print('PASS: six tower scene launches, reward transitions and exit; source/runtime unchanged') -if __name__ == "__main__": - sys.exit(main()) +if __name__ == '__main__': + main() diff --git a/tools/tests/unavailable_test.inc b/tools/tests/unavailable_test.inc index b0f17ef..c464c3e 100644 --- a/tools/tests/unavailable_test.inc +++ b/tools/tests/unavailable_test.inc @@ -1,5 +1,7 @@ .data -unavailableTestIds3D dd INTERACT_ONLINE_RR,INTERACT_UMAMUSUME,INTERACT_HELL_TOWER,INTERACT_TERMINAL,INTERACT_OBSERVER +; Tower, terminals and observer now have native scenes, not placeholder modals. +unavailableTestIds3D dd INTERACT_ONLINE_RR,INTERACT_UMAMUSUME +unavailableTestCount3D equ ($-unavailableTestIds3D)/4 unavailableTestPath3D db "build\actions\unavailable.bmp",0 .code RunUnavailableTest3D PROC uses ebx esi edi @@ -72,7 +74,7 @@ unavailable_test_close: cmp playerAngle,eax jne unavailable_test_fail inc esi - cmp esi,5 + cmp esi,unavailableTestCount3D jb unavailable_test_next mov eax,oldMode mov engineMode,eax