feat: rework artifacts and tower relics

Extend profiles from 280 to 312 bytes with legacy compatibility.
Keep tower relics run-local and share the profile bankroll.
This commit is contained in:
Игорь Федосеев 2026-10-30 23:22:19 +03:00
parent 2aa6c4cff4
commit eaac8b861e
47 changed files with 1946 additions and 278 deletions

3
.gitignore vendored
View file

@ -17,6 +17,9 @@ __pycache__/
# Generated build/test output; keep packaging source and shipping exe files. # Generated build/test output; keep packaging source and shipping exe files.
/build/actions/ /build/actions/
/build/artifacts/
/build/hell-tower/
/build/test-deps/
/build/arrival/ /build/arrival/
/build/billiards/ /build/billiards/
/build/card-ranks/ /build/card-ranks/

Binary file not shown.

View file

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

Binary file not shown.

View file

@ -3283,6 +3283,7 @@ dice_check_win:
shl eax, 1 shl eax, 1
add bankroll, eax add bankroll, eax
mov dice3d_win, eax mov dice3d_win, eax
invoke TriggerCharmWinBonus,dice3d_bet
mov dice3d_state, 2 mov dice3d_state, 2
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
invoke NpcRecordRound3D,8,1 invoke NpcRecordRound3D,8,1
@ -5373,12 +5374,12 @@ check_slots_mode:
call RenderJackbox3D call RenderJackbox3D
jmp present_frame jmp present_frame
check_jackbox_done: check_jackbox_done:
cmp engineMode,MODE_DARTS cmp engineMode,MODE_DARTS
jne check_darts_done jne check_darts_done
call HandleDarts3D call HandleDarts3D
call RenderDarts3D call RenderDarts3D
jmp present_frame jmp present_frame
check_darts_done: check_darts_done:
cmp engineMode,MODE_FORTUNE cmp engineMode,MODE_FORTUNE
jne @F jne @F
call HandleFortuneDialog3D call HandleFortuneDialog3D
@ -5659,7 +5660,7 @@ engine_shutdown:
call SaveProgress3D call SaveProgress3D
invoke PlayCasinoSound3D, 0, 0, 0 invoke PlayCasinoSound3D, 0, 0, 0
call FreeRussianAssets3D call FreeRussianAssets3D
call FreeDarts3D call FreeDarts3D
call FreeStartupAssets3D call FreeStartupAssets3D
call FreeCardTableAssets3D call FreeCardTableAssets3D
call FreeBuckshotAssets3D call FreeBuckshotAssets3D
@ -5687,3 +5688,7 @@ IFDEF CASINO_ACTION_TEST
include tools\tests\casino_actions_test.inc include tools\tests\casino_actions_test.inc
ENDIF ENDIF
IFDEF ARTIFACT_TEST
include tools\tests\artifacts_test.inc
ENDIF

Binary file not shown.

View file

@ -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%
прохождений освоившим правила игроком пока не подтверждены статистикой;
полноценная оценка требует серий забегов с указанной стратегией на каждом этаже.
Эта переработка добавляет страховку и повторы, но не подменяет случайные исходы.

View file

@ -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 First-person roulette actions need approved, consistent hand/gun animation
sequences. The requested replacement sheet failed generation and is not shipped. 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, Multiway poker requires actual 3/4-player betting, side pots and AI seat state,
not decorative extra opponents. Native talisman effects, achievements, daily not decorative extra opponents. Native talisman effects were subsequently
tasks and online tables still require integration with the selected profile. 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 old installer has not been repackaged with this work.
The classic 777 cabinet is now playable natively: generated cabinet/symbol art, The classic 777 cabinet is now playable natively: generated cabinet/symbol art,

View file

@ -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 `check_clover.py --parity` checks assets, real framebuffer clipping, all fifteen
complete cells, selected paid-mask changes and tested/shipping assembly code. 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 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 classify only positive net results as wins. No claim of completing every legacy
Clover feature or its balance is made. Clover feature or its balance is made.

View file

@ -1,13 +1,16 @@
# Native scene fixes # Native scene fixes
The MASM game stays in one graphical window. Unported CloverPit, The MASM game stays in one graphical window. Unported
online roulette, Umamusume, tower, terminal and observer interactions open a online roulette and Umamusume interactions open a
native "not implemented yet" panel. Escape, E, Enter, Space or its close button 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 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 console routine or blocks the window event loop. Legacy console game code
is retained for the earlier standalone console mode, not called by these is retained for the earlier standalone console mode, not called by these
graphical interactions. 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 Durak now has a native animated table instead of that panel, including
podkidnoy/perevodnoy rules and exit confirmation. See `native-durak.md`. podkidnoy/perevodnoy rules and exit confirmation. See `native-durak.md`.
777 now has an animated native cabinet, paytable and safely queued exit. 777 now has an animated native cabinet, paytable and safely queued exit.

View file

@ -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 This is not a completed migration of the whole casino. The implemented
graphical games include the three card tables, Russian roulette, Buckshot, graphical games include the three card tables, Russian roulette, Buckshot,
multi-bet casino roulette, coin, dice, skulls, skill checks and billiards. multi-bet casino roulette, coin, dice, skulls, skill checks and billiards.
777, CloverPit, online play, tower, terminal and observer still show native 777, CloverPit, tower, terminal and observer subsequently received native
unavailable panels. Native profile data remains separate from legacy console 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. account saves. The old installer is not refreshed by this change.

View file

@ -1,6 +1,8 @@
.386 .386
.model flat, stdcall .model flat, stdcall
option casemap:none option casemap:none
ArtifactSaveSignature3D PROTO :DWORD
ValidateArtifactSave3D PROTO :DWORD
includelib kernel32.lib includelib kernel32.lib
includelib winmm.lib includelib winmm.lib
@ -24,7 +26,9 @@ FILE_ATTRIBUTE_NORMAL equ 80h
INVALID_HANDLE_VALUE equ -1 INVALID_HANDLE_VALUE equ -1
SAVE_MAGIC equ 31435653h SAVE_MAGIC equ 31435653h
SAVE_SIGN_SIZE equ 276 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 SAVE_SALT equ 0A57C0DEh
ENABLE_VIRTUAL_TERMINAL_PROCESSING equ 0004h ENABLE_VIRTUAL_TERMINAL_PROCESSING equ 0004h
COLOR_DEFAULT equ 07h COLOR_DEFAULT equ 07h
@ -576,6 +580,7 @@ ReadKey ENDP
ResetProfileDefaults PROC uses ecx edi ResetProfileDefaults PROC uses ecx edi
call ResetArtifacts3D
mov bankroll, 10000 mov bankroll, 10000
mov targetBank, 50000 mov targetBank, 50000
mov gamesPlayed, 0 mov gamesPlayed, 0
@ -804,6 +809,20 @@ save_stats_played_loop:
loop save_stats_played_loop loop save_stats_played_loop
call CalcSaveSignature call CalcSaveSignature
mov DWORD PTR [saveBuffer+SAVE_SIGN_SIZE], eax 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 call BuildSaveFullPath
invoke CreateFileA, OFFSET saveFullPath, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 invoke CreateFileA, OFFSET saveFullPath, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
cmp eax, INVALID_HANDLE_VALUE cmp eax, INVALID_HANDLE_VALUE
@ -816,6 +835,7 @@ save_done:
SaveGame ENDP SaveGame ENDP
LoadSave PROC uses eax ecx esi edi LoadSave PROC uses eax ecx esi edi
call ResetArtifacts3D
mov dailyDateKey, 0 mov dailyDateKey, 0
mov dailyRewardMask, 0 mov dailyRewardMask, 0
mov dailyProgressPacked, 0 mov dailyProgressPacked, 0
@ -844,12 +864,29 @@ load_file_opened:
mov eax, DWORD PTR [saveBuffer] mov eax, DWORD PTR [saveBuffer]
cmp eax, SAVE_MAGIC cmp eax, SAVE_MAGIC
jne load_done jne load_done
cmp charsRead, SAVE_FILE_SIZE cmp charsRead, SAVE_LEGACY_SIZE
jb load_signature_ok jb load_signature_ok
mov edx, DWORD PTR [saveBuffer+SAVE_SIGN_SIZE] mov edx, DWORD PTR [saveBuffer+SAVE_SIGN_SIZE]
call CalcSaveSignature call CalcSaveSignature
cmp eax, edx cmp eax, edx
jne load_tampered 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: load_signature_ok:
mov eax, DWORD PTR [saveBuffer+4] mov eax, DWORD PTR [saveBuffer+4]
mov bankroll, eax mov bankroll, eax
@ -1905,8 +1942,8 @@ CheckDurakEnd ENDP
include casino3d.inc include casino3d.inc
IFDEF CASINO_ACTION_TEST IFDEF HELL_TOWER_TEST
RunCasinoActionsTest3D PROC uses ebx esi edi RunHellTowerTest3D PROC uses ebx esi edi
local stageLoop:DWORD local stageLoop:DWORD
call InitPaths3D call InitPaths3D
@ -1914,8 +1951,12 @@ RunCasinoActionsTest3D PROC uses ebx esi edi
test eax, eax test eax, eax
jz test_error_init jz test_error_init
; Test-owned session: no persistence or profile money is loaded.
mov arrivalConfirmed3D,0
mov bankroll,10000
; Enter Hell Tower ; Enter Hell Tower
call InitHellTower3D call InitHellTower3D
invoke ChooseTowerRelic3D,0
cmp tournamentMode, 1 cmp tournamentMode, 1
jne test_error_tower_mode jne test_error_tower_mode
@ -1947,6 +1988,7 @@ stage_test_loop:
call Present3D call Present3D
; Advance to next stage ; Advance to next stage
invoke ChooseTowerRelic3D,0
inc towerStage inc towerStage
mov towerFloorState, TOWER_STATE_BRIEFING mov towerFloorState, TOWER_STATE_BRIEFING
mov towerCalloutAnim, 0 mov towerCalloutAnim, 0
@ -1998,10 +2040,18 @@ test_error_exit_mode:
test_error_exit_engine: test_error_exit_engine:
mov eax, 107 mov eax, 107
ret ret
RunCasinoActionsTest3D ENDP RunHellTowerTest3D ENDP
ENDIF ENDIF
main PROC 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 IFDEF CASINO_ACTION_TEST
call RunCasinoActionsTest3D call RunCasinoActionsTest3D
invoke ExitProcess, eax invoke ExitProcess, eax

BIN
poker.exe

Binary file not shown.

BIN
poker.obj

Binary file not shown.

View file

@ -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

View file

@ -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

View file

@ -193,6 +193,7 @@ bs_commit_cleanup:
mov bs3d_damage[edi*4], 1 mov bs3d_damage[edi*4], 1
mov bs3d_known, -1 mov bs3d_known, -1
mov bs3d_known[4], -1 mov bs3d_known[4], -1
call UpdateTowerEye3D
mov bs3d_messageTime, 1800 mov bs3d_messageTime, 1800
bs_commit_done: bs_commit_done:
ret ret
@ -236,6 +237,9 @@ bs_finish_win:
jbe @F jbe @F
mov bankroll, 1000000000 mov bankroll, 1000000000
@@: @@:
mov eax,bs3d_bet
shl eax,1
invoke TriggerCharmWinBonus,eax
invoke NpcRecordRound3D,7,1 invoke NpcRecordRound3D,7,1
call SaveProgress3D call SaveProgress3D
ret ret
@ -244,6 +248,7 @@ bs_finish_lose:
mov bs3d_result, BS_LOSE mov bs3d_result, BS_LOSE
mov bs3d_victim, 0 mov bs3d_victim, 0
mov bs3d_timer, 800 mov bs3d_timer, 800
invoke TriggerCharmLossProtection,bs3d_bet
invoke NpcRecordRound3D,7,0 invoke NpcRecordRound3D,7,0
call SaveProgress3D call SaveProgress3D
bs_finish_done: bs_finish_done:
@ -866,6 +871,7 @@ ResolveBuckshotPose3D ENDP
RenderBuckshot3D PROC uses ebx esi edi RenderBuckshot3D PROC uses ebx esi edi
local handY:DWORD,pose:DWORD,actor:DWORD local handY:DWORD,pose:DWORD,actor:DWORD
call UpdateTowerEye3D
mov animOpacity3D, 255 mov animOpacity3D, 255
cmp bs3d_room.pixels, 0 cmp bs3d_room.pixels, 0
je bs_render_error je bs_render_error

View file

@ -129,6 +129,11 @@ LeaveCardTable3D PROC
card_leave_hall: card_leave_hall:
mov cardExitActive3D, 0 mov cardExitActive3D, 0
mov paused3D, 0 mov paused3D, 0
cmp tournamentMode,1
jne @F
call TowerExitToHall
ret
@@:
mov engineMode, MODE_WALK mov engineMode, MODE_WALK
call ClearKeys3D call ClearKeys3D
invoke PlayCasinoSound3D, 0, 0, 0 invoke PlayCasinoSound3D, 0, 0, 0

View file

@ -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 DrawCharmsHUD PROC uses ebx esi edi, drawX:DWORD, drawY:DWORD
local curX:DWORD, tempBuf[16]:BYTE local curX:DWORD, tempBuf[16]:BYTE
cmp tournamentMode,0
jne hud_done
mov eax, charmCloverUses mov eax, charmCloverUses
or eax, charmHorseshoeUses or eax, charmHorseshoeUses
or eax, charmSkullUses or eax, charmSkullUses
@ -135,6 +16,8 @@ DrawCharmsHUD PROC uses ebx esi edi, drawX:DWORD, drawY:DWORD
mov curX, eax mov curX, eax
; 1. Clover Badge ; 1. Clover Badge
test charmEnabledMask3D,1
jz check_horse_hud
cmp charmCloverUses, 0 cmp charmCloverUses, 0
jle check_horse_hud jle check_horse_hud
@ -152,6 +35,8 @@ DrawCharmsHUD PROC uses ebx esi edi, drawX:DWORD, drawY:DWORD
check_horse_hud: check_horse_hud:
; 2. Horseshoe Badge ; 2. Horseshoe Badge
test charmEnabledMask3D,2
jz check_skull_hud
cmp charmHorseshoeUses, 0 cmp charmHorseshoeUses, 0
jle check_skull_hud jle check_skull_hud
@ -169,6 +54,8 @@ check_horse_hud:
check_skull_hud: check_skull_hud:
; 3. Skull Badge ; 3. Skull Badge
test charmEnabledMask3D,4
jz check_devil_hud
cmp charmSkullUses, 0 cmp charmSkullUses, 0
jle check_devil_hud jle check_devil_hud
@ -186,6 +73,8 @@ check_skull_hud:
check_devil_hud: check_devil_hud:
; 4. Devil Contract Badge ; 4. Devil Contract Badge
test charmEnabledMask3D,8
jz hud_done
cmp charmDevilUses, 0 cmp charmDevilUses, 0
jle hud_done jle hud_done

View file

@ -1,19 +1,29 @@
CHARM_COUNT equ 4 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_CLOVER_USES equ 3
CHARM_HORSESHOE_PRICE equ 20000 CHARM_HORSESHOE_PRICE equ 1800
CHARM_HORSESHOE_USES equ 5 CHARM_HORSESHOE_USES equ 5
CHARM_SKULL_PRICE equ 35000 CHARM_SKULL_PRICE equ 3500
CHARM_SKULL_USES equ 1 CHARM_SKULL_USES equ 1
CHARM_DEVIL_PRICE equ 50000 CHARM_DEVIL_PRICE equ 4000
CHARM_DEVIL_USES equ 2 CHARM_DEVIL_USES equ 2
TriggerCharmLossProtection PROTO :DWORD TriggerCharmLossProtection PROTO :DWORD
TriggerCharmWinBonus 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 TriggerCharmBulletProtection PROTO
DrawCharmsHUD PROTO :DWORD, :DWORD DrawCharmsHUD PROTO :DWORD, :DWORD
@ -24,35 +34,57 @@ charmCloverUses dd 0
charmHorseshoeUses dd 0 charmHorseshoeUses dd 0
charmSkullUses dd 0 charmSkullUses dd 0
charmDevilUses dd 0 charmDevilUses dd 0
charmEnabledMask3D dd 7
artifactBuyArmed3D dd 0
artifactNum3D db 16 dup(0)
; Charm Store Metadata ; Charm Store Metadata
charmPrices3D dd CHARM_CLOVER_PRICE, CHARM_HORSESHOE_PRICE, CHARM_SKULL_PRICE, CHARM_DEVIL_PRICE 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 charmUsesPack3D dd CHARM_CLOVER_USES, CHARM_HORSESHOE_USES, CHARM_SKULL_USES, CHARM_DEVIL_USES
charmNameClover db 'Êëåâåð Óäà÷è',0 charmNameClover db 202,203,197,194,197,208,0
charmNameHorseshoe db 'Çîëîòàÿ Ïîäêîâà',0 charmNameHorseshoe db 207,206,196,202,206,194,192,0
charmNameSkull db '×åðåï Áåññìåðòèÿ',0 charmNameSkull db 215,197,208,197,207,0
charmNameDevil db 'Êîíòðàêò Äüÿâîëà',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
charmNames3D dd OFFSET charmNameClover, OFFSET charmNameHorseshoe, OFFSET charmNameSkull, OFFSET charmNameDevil 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
charmDescClover db '15% øàíñ ñïàñòè ñòàâêó',0 charmDescDevil db 211,228,226,238,232,242,32,239,240,232,225,251,235,252,0
charmDescHorseshoe db '+15% ê âûèãðûøó â ñëîòàõ',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
charmDescSkull db '100% ñïàñåíèå îò ïóëè',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
charmDescDevil db '50% øàíñ ñïàñòè ñòàâêó',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
charmDescs3D dd OFFSET charmDescClover, OFFSET charmDescHorseshoe, OFFSET charmDescSkull, OFFSET charmDescDevil 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
; HUD Flash Messages badgeHorsePrefix db 91,207,196,58,0
msgCharmCloverSave db 'Êëåâåð ñïàñ âàøó ñòàâêó!',0 badgeSkullPrefix db 91,215,208,58,0
msgCharmDevilSave db 'Äüÿâîë: ñòàâêà âîçâðàùåíà!',0 badgeDevilPrefix db 91,196,202,58,0
msgCharmHorseshoeWin db 'Ïîäêîâà: +15% ê âûèãðûøó!',0 badgeCloseBracket db 93,32,0
msgCharmSkullSave db '×åðåï: Îñå÷êà! Âû âûæèëè!',0 artifactShopTitle3D db 192,208,210,197,212,192,202,210,219,32,209,200,196,206,208,206,194,200,215,192,0
msgCharmTowerSave db 'Òàëèñìàí ñïàñ âàñ îò ïàäåíèÿ ñ Áàøíè!',0 artifactStock3D db 197,209,210,220,58,0
artifactPack3D db 205,192,193,206,208,58,0
; Compact HUD Badges artifactPrice3D db 214,197,205,192,58,0
badgeCloverPrefix db '[ÊË:',0 artifactOn3D db 194,202,203,0
badgeHorsePrefix db '[ÏÊ:',0 artifactOff3D db 194,219,202,203,0
badgeSkullPrefix db '[×Ð:',0 artifactToggle3D db 91,69,93,32,194,202,203,32,47,32,194,219,202,203,0
badgeDevilPrefix db '[ÊÒ:',0 artifactBuy3D db 91,69,78,84,69,82,93,32,202,211,207,200,210,220,0
badgeCloseBracket db '] ',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

View file

@ -27,7 +27,7 @@ ValidateGraphicProfile3D PROC uses ebx esi edi path:DWORD
jne profile_invalid jne profile_invalid
cmp DWORD PTR entrySaveData3D[4], 1000000000 cmp DWORD PTR entrySaveData3D[4], 1000000000
ja profile_invalid ja profile_invalid
cmp fileBytes, SAVE_FILE_SIZE cmp fileBytes, SAVE_LEGACY_SIZE
jb profile_valid jb profile_valid
mov esi, OFFSET entrySaveData3D mov esi, OFFSET entrySaveData3D
mov ecx, SAVE_SIGN_SIZE/4 mov ecx, SAVE_SIGN_SIZE/4
@ -43,6 +43,13 @@ profile_signature:
loop profile_signature loop profile_signature
cmp eax, DWORD PTR entrySaveData3D[SAVE_SIGN_SIZE] cmp eax, DWORD PTR entrySaveData3D[SAVE_SIGN_SIZE]
jne profile_invalid 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: profile_valid:
mov eax, 1 mov eax, 1
ret ret
@ -253,6 +260,7 @@ graphic_create_store:
invoke CreateDirectoryA, OFFSET runtime3D, 0 invoke CreateDirectoryA, OFFSET runtime3D, 0
invoke CreateDirectoryA, nativeProfilesDirPtr3D, 0 invoke CreateDirectoryA, nativeProfilesDirPtr3D, 0
call ResetProfileDefaults call ResetProfileDefaults
call ResetArtifacts3D
invoke CopyCString, OFFSET saveFileName, OFFSET entryDisplayName3D invoke CopyCString, OFFSET saveFileName, OFFSET entryDisplayName3D
mov nativeProfileActive3D, 1 mov nativeProfileActive3D, 1
call SyncGraphicBack3D call SyncGraphicBack3D

View file

@ -12,11 +12,13 @@ FreeTowerMinigameAssets ENDP
InitHellTower3D PROC InitHellTower3D PROC
call FreeTowerMinigameAssets call FreeTowerMinigameAssets
call ResetTowerRelics3D
mov tournamentMode, 1 mov tournamentMode, 1
mov towerStage, 1 mov towerStage, 1
mov towerFloorState, TOWER_STATE_BRIEFING mov towerFloorState, TOWER_STATE_BRIEFING
mov towerAnimClock, 0 mov towerAnimClock, 0
mov towerCalloutAnim, 0 mov towerCalloutAnim, 0
call PrepareTowerReward3D
mov eax, towerFloorY[0] mov eax, towerFloorY[0]
mov towerCursorY, eax mov towerCursorY, eax
@ -33,6 +35,7 @@ InitHellTower3D ENDP
TowerExitToHall PROC TowerExitToHall PROC
call StopTowerMusic call StopTowerMusic
call FreeTowerMinigameAssets call FreeTowerMinigameAssets
call ResetTowerRelics3D
mov tournamentMode, 0 mov tournamentMode, 0
mov engineMode, MODE_WALK mov engineMode, MODE_WALK
call ClearKeys3D call ClearKeys3D
@ -40,6 +43,18 @@ TowerExitToHall PROC
TowerExitToHall ENDP TowerExitToHall ENDP
TowerLaunchCurrentFloor PROC uses ebx esi edi 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 mov eax, towerStage
dec eax dec eax
cmp eax, 6 cmp eax, 6
@ -79,6 +94,7 @@ launch_valid:
@@: @@:
cmp edx, MODE_ROULETTE cmp edx, MODE_ROULETTE
jne @F jne @F
mov wheelChip3D,100
call InitNativeWheel3D call InitNativeWheel3D
mov engineMode, MODE_ROULETTE mov engineMode, MODE_ROULETTE
jmp launch_done jmp launch_done
@ -86,16 +102,32 @@ launch_valid:
cmp edx, MODE_BUCKSHOT cmp edx, MODE_BUCKSHOT
jne launch_done jne launch_done
call InitBuckshot3D call InitBuckshot3D
test eax,eax
jnz @F
mov towerFloorState,TOWER_STATE_BRIEFING
mov engineMode,MODE_HELL_TOWER
ret
@@:
mov engineMode, MODE_BUCKSHOT mov engineMode, MODE_BUCKSHOT
launch_done: 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 call ClearKeys3D
ret ret
tower_launch_blocked:
ret
TowerLaunchCurrentFloor ENDP TowerLaunchCurrentFloor ENDP
TowerReportRoundOutcome PROC uses eax outcome:DWORD TowerReportRoundOutcome PROC uses eax outcome:DWORD
cmp tournamentMode, 1 cmp tournamentMode, 1
jne report_exit jne report_exit
cmp towerFloorState,TOWER_STATE_IN_GAME
jne report_exit
mov eax, outcome mov eax, outcome
cmp eax, 1 cmp eax, 1
jne report_loss jne report_loss
@ -105,6 +137,7 @@ TowerReportRoundOutcome PROC uses eax outcome:DWORD
jae report_champion jae report_champion
mov towerFloorState, TOWER_STATE_CLEAR mov towerFloorState, TOWER_STATE_CLEAR
call PrepareTowerReward3D
mov towerCalloutAnim, 0 mov towerCalloutAnim, 0
mov eax, towerStage mov eax, towerStage
dec eax dec eax
@ -120,8 +153,8 @@ TowerReportRoundOutcome PROC uses eax outcome:DWORD
report_champion: report_champion:
mov towerFloorState, TOWER_STATE_CHAMPION mov towerFloorState, TOWER_STATE_CHAMPION
mov eax, towerPrizeAmount invoke ArtifactCredit3D,towerPrizeAmount
add bankroll, eax call ResetTowerRelics3D
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
call SaveProgress3D call SaveProgress3D
mov engineMode, MODE_HELL_TOWER mov engineMode, MODE_HELL_TOWER
@ -129,16 +162,39 @@ report_champion:
ret ret
report_loss: report_loss:
invoke TriggerCharmLossProtection, 0 cmp towerPurseUses3D,0
test eax, eax je tower_loss_retry
jz @F mov eax,towerEntryBank3D
mov towerFloorState, TOWER_STATE_BRIEFING sub eax,bankroll
invoke CopyString3D, OFFSET msgCharmTowerSave, OFFSET flashMessage jbe tower_loss_retry
mov flashTimer, 140 cmp eax,5000
mov engineMode, MODE_HELL_TOWER 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 call ClearKeys3D
ret ret
@@: tower_loss_final:
call ResetTowerRelics3D
mov towerFloorState, TOWER_STATE_DEFEAT mov towerFloorState, TOWER_STATE_DEFEAT
call StopTowerMusic call StopTowerMusic
invoke PlayCasinoSound3D, OFFSET soundRoundLose, 0, 1 invoke PlayCasinoSound3D, OFFSET soundRoundLose, 0, 1
@ -151,6 +207,11 @@ report_exit:
TowerReportRoundOutcome ENDP TowerReportRoundOutcome ENDP
HandleHellTowerInput PROC HandleHellTowerInput PROC
cmp towerRewardPending3D,1
jne @F
call HandleTowerReward3D
ret
@@:
mov eax, towerFloorState mov eax, towerFloorState
cmp eax, TOWER_STATE_BRIEFING cmp eax, TOWER_STATE_BRIEFING
je input_briefing je input_briefing
@ -240,6 +301,11 @@ HandleHellTowerInput ENDP
RenderHellTower3D PROC uses ebx esi edi RenderHellTower3D PROC uses ebx esi edi
local cardX:DWORD, cardY:DWORD, cardW:DWORD, cardH:DWORD local cardX:DWORD, cardY:DWORD, cardW:DWORD, cardH:DWORD
local currY:DWORD, tgtY: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 ; 1. Background: draw HellTower menu art if loaded, else fallback to maroon
cmp hellTowerMenuArt3D.pixels, 0 cmp hellTowerMenuArt3D.pixels, 0
@ -456,10 +522,18 @@ render_card_briefing:
invoke DrawBoxBorder, eax, 134, ecx, 32, 00180303h, 00661111h, 1 invoke DrawBoxBorder, eax, 134, ecx, 32, 00180303h, 00661111h, 1
mov eax, cardX mov eax, cardX
add eax, 12 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 mov eax, cardX
add eax, 12 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) ; Bankroll & Prize (Y = 172)
mov eax, cardX mov eax, cardX
@ -552,6 +626,7 @@ render_card_champion:
invoke DrawString8x8, eax, 184, OFFSET towerReturnBtn, 00FFFFFFh invoke DrawString8x8, eax, 184, OFFSET towerReturnBtn, 00FFFFFFh
render_tower_done: render_tower_done:
call RenderTowerRelicStock3D
ret ret
RenderHellTower3D ENDP RenderHellTower3D ENDP
@ -568,7 +643,10 @@ RenderTowerInGameBadge PROC
invoke FormatNumber, towerStage, OFFSET hudNumBuffer invoke FormatNumber, towerStage, OFFSET hudNumBuffer
invoke DrawString8x8, 64, 8, OFFSET hudNumBuffer, 00FFFFFFh invoke DrawString8x8, 64, 8, OFFSET hudNumBuffer, 00FFFFFFh
invoke DrawString8x8, 72, 8, OFFSET towerHudOfTen, 00AAAAAAh invoke DrawString8x8, 72, 8, OFFSET towerHudOfTen, 00AAAAAAh
call RenderTowerRelicStock3D
badge_done: badge_done:
ret ret
RenderTowerInGameBadge ENDP RenderTowerInGameBadge ENDP
include src_graphics\tower_relics.inc

View file

@ -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 towerHudBadge db 193,192,216,205,223,58,32,0
towerHudOfTen db 47,54,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

View file

@ -4,7 +4,8 @@ BeginMerchant3D PROC
mov merchantTime3D, 0 mov merchantTime3D, 0
mov merchantReaction3D, 1280 mov merchantReaction3D, 1280
mov merchantMessage3D, 0 mov merchantMessage3D, 0
mov merchantTab3D, 0 mov merchantTab3D, 1
mov artifactBuyArmed3D,0
mov merchantCharmSelected3D, 0 mov merchantCharmSelected3D, 0
mov eax, selectedBack3D mov eax, selectedBack3D
cmp eax, 12 cmp eax, 12
@ -48,49 +49,40 @@ merchant_purchase_done:
ret ret
BuyMerchantBack3D ENDP BuyMerchantBack3D ENDP
BuyMerchantCharm3D PROC uses ebx charmIndex:DWORD BuyMerchantCharm3D PROC uses ebx esi charmIndex:DWORD
mov ebx, charmIndex mov ebx,charmIndex
cmp ebx, 3 cmp ebx,CHARM_COUNT
ja charm_purchase_done jae charm_purchase_done
mov eax, charmPrices3D[ebx*4] lea esi,charmCloverUses
cmp bankroll, eax 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 jb charm_purchase_poor
sub bankroll, eax sub bankroll,eax
mov ecx, charmUsesPack3D[ebx*4] mov eax,charmUsesPack3D[ebx*4]
cmp ebx, 0 add [esi+ebx*4],eax
jne @F mov merchantMessage3D,1
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
call SaveProgress3D 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 ret
charm_purchase_poor: charm_purchase_poor:
mov merchantMessage3D, 2 mov merchantMessage3D,2
mov merchantReaction3D, 0
mov merchantReactionKind3D, 1
charm_purchase_done: charm_purchase_done:
ret ret
BuyMerchantCharm3D ENDP BuyMerchantCharm3D ENDP
HandleMerchant3D PROC uses ebx HandleMerchant3D PROC uses ebx
cmp merchantTab3D,1
jne @F
call HandleArtifactShop3D
ret
@@:
mov eax, merchantSelected3D mov eax, merchantSelected3D
cmp eax, merchantPreviousFocus3D cmp eax, merchantPreviousFocus3D
je @F je @F
@ -256,6 +248,11 @@ HandleMerchant3D ENDP
RenderMerchant3D PROC uses ebx esi edi RenderMerchant3D PROC uses ebx esi edi
local rowY:DWORD,start:DWORD,frame:DWORD,ink:DWORD 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, 0, 320, 240, 00252B24h
invoke DrawBox, 0, 21, 320, 7, 00836638h invoke DrawBox, 0, 21, 320, 7, 00836638h
invoke DrawBox, 0, 151, 320, 63, 003C3024h invoke DrawBox, 0, 151, 320, 63, 003C3024h
@ -451,3 +448,5 @@ merchant_render_footer:
invoke DrawString8x8, 8, 229, OFFSET merchantShopKeysWithTabs, 00C5C7B8h invoke DrawString8x8, 8, 229, OFFSET merchantShopKeysWithTabs, 00C5C7B8h
ret ret
RenderMerchant3D ENDP RenderMerchant3D ENDP
include src_graphics\artifact_shop.inc

View file

@ -75,6 +75,8 @@ NativeBJSettle3D PROC result:DWORD
add eax, edx add eax, edx
bj_settle_credit: bj_settle_credit:
add bankroll, eax add bankroll, eax
sub eax,cbStake3D
invoke TriggerCharmWinBonus,eax
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
jmp bj_settle_save jmp bj_settle_save
bj_settle_loss: bj_settle_loss:
@ -230,6 +232,9 @@ bj_animate_hit:
jne bj_begin_dealer jne bj_begin_dealer
jmp bj_begin_player jmp bj_begin_player
bj_player_bust: bj_player_bust:
call TryTowerBustSave3D
test eax,eax
jnz bj_begin_dealer
invoke FinishBJPlayer3D, 1 invoke FinishBJPlayer3D, 1
ret ret
bj_animate_dealer: bj_animate_dealer:
@ -327,6 +332,13 @@ bj_ready_input:
jnz bj_tourney_report jnz bj_tourney_report
ret ret
bj_tourney_report: bj_tourney_report:
cmp cbOutcome3D,2
jne @F
mov towerFloorState,TOWER_STATE_BRIEFING
mov engineMode,MODE_HELL_TOWER
call ClearKeys3D
ret
@@:
cmp cbOutcome3D, 1 cmp cbOutcome3D, 1
je bj_tourney_loss je bj_tourney_loss
invoke TowerReportRoundOutcome, 1 invoke TowerReportRoundOutcome, 1

View file

@ -186,10 +186,14 @@ SettleNativeClover3D PROC
jmp clp_settle_leave jmp clp_settle_leave
@@: @@:
invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1 invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1
invoke TriggerCharmWinBonus, clpGross3D mov eax,clpGross3D
sub eax,clpStake3D
invoke TriggerCharmWinBonus,eax
jmp clp_settle_leave jmp clp_settle_leave
clp_settle_loss: clp_settle_loss:
invoke TriggerCharmLossProtection, clpStake3D mov eax,clpStake3D
sub eax,clpGross3D
invoke TriggerCharmLossProtection,eax
test eax, eax test eax, eax
jnz clp_settle_leave jnz clp_settle_leave
invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1 invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1

View file

@ -58,6 +58,9 @@ CoinEscape3D PROC
mov paused3D,0 mov paused3D,0
cmp tournamentMode, 1 cmp tournamentMode, 1
jne @F jne @F
call CoinSettle3D
call CloseCoinDetail3D
mov coinExitQueued3D,0
call TowerExitToHall call TowerExitToHall
ret ret
@@: @@:
@ -96,6 +99,7 @@ CoinSettle3D PROC
jbe @F jbe @F
mov bankroll,1000000000 mov bankroll,1000000000
@@: @@:
invoke TriggerCharmWinBonus,coin3d_bet
invoke PlayCasinoSound3D,OFFSET soundCoinDing,0,1 invoke PlayCasinoSound3D,OFFSET soundCoinDing,0,1
jmp coin_settle_save jmp coin_settle_save
coin_settle_loss: coin_settle_loss:

View file

@ -35,6 +35,11 @@ DiceEscape3D PROC
ret ret
dice_escape_now: dice_escape_now:
mov diceExitQueued3D,0 mov diceExitQueued3D,0
cmp tournamentMode,1
jne @F
call TowerExitToHall
ret
@@:
mov engineMode,MODE_WALK mov engineMode,MODE_WALK
call ClearKeys3D call ClearKeys3D
invoke PlayCasinoSound3D,0,0,0 invoke PlayCasinoSound3D,0,0,0

View file

@ -127,6 +127,8 @@ NativeDurakSettle3D PROC result:DWORD
shl eax, 1 shl eax, 1
@@: @@:
add bankroll, eax add bankroll, eax
sub eax,dkStake3D
invoke TriggerCharmWinBonus,eax
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
jmp dk_settle_save jmp dk_settle_save
dk_settle_loss: dk_settle_loss:

View file

@ -255,6 +255,8 @@ PokerFold3D PROC actor:DWORD
je poker_fold_player je poker_fold_player
mov eax, pkPot3D mov eax, pkPot3D
add bankroll, eax add bankroll, eax
sub eax,pkPaid3D[0]
invoke TriggerCharmWinBonus,eax
mov pkOutcome3D, 0 mov pkOutcome3D, 0
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
jmp poker_fold_settled jmp poker_fold_settled
@ -343,6 +345,8 @@ poker_evaluate_player:
je poker_showdown_tie je poker_showdown_tie
mov eax, pkPot3D mov eax, pkPot3D
add bankroll, eax add bankroll, eax
sub eax,pkPaid3D[0]
invoke TriggerCharmWinBonus,eax
mov pkOutcome3D, 0 mov pkOutcome3D, 0
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1 invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
jmp poker_showdown_settled jmp poker_showdown_settled

View file

@ -173,10 +173,14 @@ slot_settle_stops:
jmp slot_settle_exit jmp slot_settle_exit
@@: @@:
invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1 invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,1
invoke TriggerCharmWinBonus, slotWinAmount mov eax,slotWinAmount
sub eax,slotStake3D
invoke TriggerCharmWinBonus,eax
jmp slot_settle_exit jmp slot_settle_exit
slot_settle_loss: slot_settle_loss:
invoke TriggerCharmLossProtection, slotStake3D mov eax,slotStake3D
sub eax,slotWinAmount
invoke TriggerCharmLossProtection,eax
test eax, eax test eax, eax
jnz slot_settle_exit jnz slot_settle_exit
invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1 invoke PlayCasinoSound3D,OFFSET soundRoundLose,0,1

View file

@ -134,12 +134,15 @@ wheel_bank_cap:
mov ecx, wheelReturn3D mov ecx, wheelReturn3D
cmp ecx, wheelStake3D cmp ecx, wheelStake3D
ja wheel_win_charm ja wheel_win_charm
cmp ecx, 0 je wheel_charm_done
jne wheel_charm_done mov eax,wheelStake3D
invoke TriggerCharmLossProtection, wheelStake3D sub eax,wheelReturn3D
invoke TriggerCharmLossProtection,eax
jmp wheel_charm_done jmp wheel_charm_done
wheel_win_charm: wheel_win_charm:
invoke TriggerCharmWinBonus, wheelReturn3D mov eax,wheelReturn3D
sub eax,wheelStake3D
invoke TriggerCharmWinBonus,eax
wheel_charm_done: wheel_charm_done:
mov roulState, 2 mov roulState, 2
xor eax,eax xor eax,eax
@ -519,6 +522,11 @@ WheelEscape3D PROC
ret ret
wheel_leave_hall: wheel_leave_hall:
mov wheelExitQueued3D,0 mov wheelExitQueued3D,0
cmp tournamentMode,1
jne @F
call TowerExitToHall
ret
@@:
mov engineMode,MODE_WALK mov engineMode,MODE_WALK
call ClearKeys3D call ClearKeys3D
invoke PlayCasinoSound3D,0,0,0 invoke PlayCasinoSound3D,0,0,0

View file

@ -549,14 +549,26 @@ LoadProgress3D PROC uses ebx
mov charmHorseshoeUses, 0 mov charmHorseshoeUses, 0
mov charmSkullUses, 0 mov charmSkullUses, 0
mov charmDevilUses, 0 mov charmDevilUses, 0
mov charmEnabledMask3D,7
invoke CreateFileA, OFFSET savePath3D, 80000000h, 1, 0, 3, 80h, 0 invoke CreateFileA, OFFSET savePath3D, 80000000h, 1, 0, 3, 80h, 0
cmp eax, -1 cmp eax, -1
je progress_loaded je progress_loaded
mov ebx, eax mov ebx, eax
invoke ReadFile, ebx, OFFSET saveData3D, 32, OFFSET saveIo3D, 0 invoke ReadFile, ebx, OFFSET saveData3D, 40, OFFSET saveIo3D, 0
invoke CloseHandle, ebx invoke CloseHandle, ebx
cmp saveIo3D, 16 cmp saveIo3D, 16
jb progress_loaded 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 cmp saveData3D, 33443343h
je progress_version_ok je progress_version_ok
cmp saveData3D, 32443343h cmp saveData3D, 32443343h
@ -607,6 +619,7 @@ progress_version_ok:
mov eax, saveData3D[28] mov eax, saveData3D[28]
mov charmDevilUses, eax mov charmDevilUses, eax
progress_loaded: progress_loaded:
call ClampArtifacts3D
ret ret
LoadProgress3D ENDP LoadProgress3D ENDP
@ -625,7 +638,7 @@ SaveProgress3D PROC uses ebx
ret ret
@@: @@:
invoke CreateDirectoryA, OFFSET runtime3D, 0 invoke CreateDirectoryA, OFFSET runtime3D, 0
mov saveData3D, 33443343h mov saveData3D, 34443343h
mov eax, bankroll mov eax, bankroll
mov saveData3D[4], eax mov saveData3D[4], eax
mov eax, ownedBacks3D mov eax, ownedBacks3D
@ -640,11 +653,15 @@ SaveProgress3D PROC uses ebx
mov saveData3D[24], eax mov saveData3D[24], eax
mov eax, charmDevilUses mov eax, charmDevilUses
mov saveData3D[28], eax 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 invoke CreateFileA, OFFSET savePath3D, 40000000h, 0, 0, 2, 80h, 0
cmp eax, -1 cmp eax, -1
je progress_saved je progress_saved
mov ebx, eax mov ebx, eax
invoke WriteFile, ebx, OFFSET saveData3D, 32, OFFSET saveIo3D, 0 invoke WriteFile, ebx, OFFSET saveData3D, 40, OFFSET saveIo3D, 0
invoke CloseHandle, ebx invoke CloseHandle, ebx
progress_saved: progress_saved:
ret ret

View file

@ -66,7 +66,7 @@ displaySettingsPath3D db "runtime\display.cfg",0
displaySettingsData3D dd 44535031h,1,0 displaySettingsData3D dd 44535031h,1,0
displaySettingsIo3D dd 0 displaySettingsIo3D dd 0
savePath3D db "runtime\casino3d-progress.dat",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 saveIo3D dd 0
previewBackMap3D dd 0,3,5,2 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 textPause3D db 202,192,199,200,205,206,32,47,32,205,192,209,210,208,206,201,202,200,0

View file

@ -81,6 +81,8 @@ RRPartyWin3D PROC
imul eax,rrPartySize3D imul eax,rrPartySize3D
mov rr3d_payout,eax mov rr3d_payout,eax
add bankroll,eax add bankroll,eax
sub eax,rrPartyStake3D
invoke TriggerCharmWinBonus,eax
mov rr3d_state,RR_WIN mov rr3d_state,RR_WIN
invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,20003h invoke PlayCasinoSound3D,OFFSET soundRoundWin,0,20003h
invoke NpcRecordRound3D,6,1 invoke NpcRecordRound3D,6,1
@ -93,6 +95,7 @@ RRPartyLose3D PROC
cmp rrPartySettled3D,0 cmp rrPartySettled3D,0
jne rr_party_lose_done jne rr_party_lose_done
mov rrPartySettled3D,1 mov rrPartySettled3D,1
invoke TriggerCharmLossProtection,rrPartyStake3D
mov rr3d_state,RR_GLITCH mov rr3d_state,RR_GLITCH
invoke NpcRecordRound3D,6,0 invoke NpcRecordRound3D,6,0
call SaveProgress3D call SaveProgress3D

View file

@ -185,12 +185,14 @@ rr_live_round:
add bankroll, eax add bankroll, eax
mov rr3d_state, RR_WIN mov rr3d_state, RR_WIN
mov rrPartySettled3D,1 mov rrPartySettled3D,1
invoke TriggerCharmWinBonus,rr3d_bet
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 20003h invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 20003h
invoke NpcRecordRound3D,6,1 invoke NpcRecordRound3D,6,1
call SaveProgress3D call SaveProgress3D
ret ret
rr_player_lost: rr_player_lost:
mov rrPartySettled3D,1 mov rrPartySettled3D,1
invoke TriggerCharmLossProtection,rr3d_bet
mov rr3d_state, RR_GLITCH mov rr3d_state, RR_GLITCH
invoke NpcRecordRound3D,6,0 invoke NpcRecordRound3D,6,0
call SaveProgress3D call SaveProgress3D

View file

@ -413,12 +413,15 @@ FinishScratch3D PROC
mov ecx, scrGross3D mov ecx, scrGross3D
cmp ecx, scrStake3D cmp ecx, scrStake3D
ja scratch_charm_win ja scratch_charm_win
cmp ecx, 0 je @F
jne @F mov eax,scrStake3D
invoke TriggerCharmLossProtection, scrStake3D sub eax,scrGross3D
invoke TriggerCharmLossProtection,eax
jmp @F jmp @F
scratch_charm_win: scratch_charm_win:
invoke TriggerCharmWinBonus, scrGross3D mov eax,scrGross3D
sub eax,scrStake3D
invoke TriggerCharmWinBonus,eax
@@: @@:
call ScratchStartPayout3D call ScratchStartPayout3D
call SaveProgress3D call SaveProgress3D

View file

@ -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

View file

@ -640,6 +640,7 @@ arrival_admit_sequence:
jne arrival_test_fail jne arrival_test_fail
mov bankroll, 35000 mov bankroll, 35000
call BeginMerchant3D call BeginMerchant3D
mov merchantTab3D,0
mov merchantSelected3D, 5 mov merchantSelected3D, 5
mov merchantReaction3D, 1280 mov merchantReaction3D, 1280
mov eax, bankroll mov eax, bankroll

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -9,6 +9,7 @@ from PIL import Image, ImageStat, ImageChops
from check_card_exit_build import Coff from check_card_exit_build import Coff
from check_terminals import signature from check_terminals import signature
from check_character_cast import check as check_character_cast from check_character_cast import check as check_character_cast
from check_artifacts import toolchain
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / "build/hall-expansion" OUT = ROOT / "build/hall-expansion"
@ -329,11 +330,11 @@ def main():
(OUT/(stem+".asm")).write_text(source,encoding="cp1251") (OUT/(stem+".asm")).write_text(source,encoding="cp1251")
masm = Path("F:/MASM32/masm32") 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") 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.", 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) 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] 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"/MAP:build/hall-expansion/{stem}.map",
f"/LIBPATH:{sdk}",f"/LIBPATH:{masm/'lib'}",f"/OUT:build/hall-expansion/{stem}.exe", 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", f"build/hall-expansion/{stem}.obj","build/hall-expansion/helpers.obj",

View file

@ -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('<I', len(hands))+vectors)
print(f'Prepared {len(hands)} independent poker vectors')
if __name__ == '__main__':
main()

View file

@ -0,0 +1,3 @@
Pillow>=10
capstone>=5,<6
treys==0.1.8

View file

@ -1,45 +1,34 @@
"""Automated smoke test for Hell Tower 6-floor run-through.""" """Smoke-test six real tower scene launches; outcomes injected, not balance measurement."""
import subprocess import hashlib
import sys
from pathlib import Path from pathlib import Path
import subprocess
from check_artifacts import toolchain
ROOT = Path(__file__).resolve().parents[2] ROOT = Path(__file__).resolve().parents[2]
POKER_ASM = ROOT / "poker.asm" OUT = ROOT/'build/hell-tower'
def main(): def main():
print("[1/4] Preparing test harness in poker.asm...") OUT.mkdir(parents=True, exist_ok=True)
code = POKER_ASM.read_text(encoding="cp1251") source_hash = hashlib.sha256((ROOT/'poker.asm').read_bytes()).digest()
target = "\nIFDEF CASINO_ACTION_TEST\n" runtime = {p: hashlib.sha256(p.read_bytes()).digest()
if "CASINO_ACTION_TEST equ 1\n" not in code: for p in (ROOT/'runtime').rglob('*') if p.is_file()}
code = code.replace(target, "\nCASINO_ACTION_TEST equ 1\nIFDEF CASINO_ACTION_TEST\n") ml, link = toolchain()
POKER_ASM.write_text(code, encoding="cp1251") sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib')
libraries = next(p/'um/x86' for p in sorted(sdk.iterdir(), reverse=True)
try: if (p/'um/x86/kernel32.lib').exists())
print("[2/4] Building poker.exe with test harness...") subprocess.run([str(ml), '/c', '/coff', '/Cp', '/nologo', '/I.', '/DHELL_TOWER_TEST=1',
build_res = subprocess.run(["cmd.exe", "/c", "build.bat"], cwd=ROOT, capture_output=True, text=True) '/Fobuild/hell-tower/test.obj', 'poker.asm'], cwd=ROOT, check=True)
if build_res.returncode != 0: subprocess.run([str(link), '/nologo', '/SUBSYSTEM:CONSOLE', '/LIBPATH:'+str(libraries),
print("[FAIL] Build failed:", build_res.stdout, build_res.stderr) '/OUT:build/hell-tower/test.exe', 'build/hell-tower/test.obj',
return 1 'kernel32.lib', 'winmm.lib', 'user32.lib', 'gdi32.lib', 'opengl32.lib'],
cwd=ROOT, check=True)
print("[3/4] Running Hell Tower 6-floor run-through...") subprocess.run([str(OUT/'test.exe')], cwd=ROOT, check=True, timeout=90)
run_res = subprocess.run([str(ROOT / "poker.exe")], cwd=ROOT, capture_output=True, timeout=15) assert hashlib.sha256((ROOT/'poker.asm').read_bytes()).digest() == source_hash
print(f"Hell Tower test completed with exit code: {run_res.returncode}") assert {p: hashlib.sha256(p.read_bytes()).digest()
if run_res.returncode != 0: for p in (ROOT/'runtime').rglob('*') if p.is_file()} == runtime
print(f"[FAIL] Hell Tower run-through failed with code {run_res.returncode}") print('PASS: six tower scene launches, reward transitions and exit; source/runtime unchanged')
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
if __name__ == "__main__": if __name__ == '__main__':
sys.exit(main()) main()

View file

@ -1,5 +1,7 @@
.data .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 unavailableTestPath3D db "build\actions\unavailable.bmp",0
.code .code
RunUnavailableTest3D PROC uses ebx esi edi RunUnavailableTest3D PROC uses ebx esi edi
@ -72,7 +74,7 @@ unavailable_test_close:
cmp playerAngle,eax cmp playerAngle,eax
jne unavailable_test_fail jne unavailable_test_fail
inc esi inc esi
cmp esi,5 cmp esi,unavailableTestCount3D
jb unavailable_test_next jb unavailable_test_next
mov eax,oldMode mov eax,oldMode
mov engineMode,eax mov engineMode,eax