feat: add startup briefing and entry music
Sequence studio, texture AI disclosure and MASM code comparison before arrival. Show a typed hall briefing after admission. Start the jukebox at 50% user volume on hall entry. Apply positional gain before playback to avoid a full-volume startup burst.
This commit is contained in:
parent
3150d217b5
commit
24d66ddf0e
22 changed files with 1171 additions and 7 deletions
|
|
@ -33,11 +33,18 @@ $6,660; death/retry preserves the casino bank. See
|
|||
|
||||
## Native Entrance And Character Scenes
|
||||
|
||||
The graphical startup now fades from Eblan Games into a first-person opening:
|
||||
The graphical startup runs Eblan Games, a Russian AI-use disclosure and a
|
||||
side-by-side C++/MASM code fact before the first-person opening:
|
||||
eyes opening beneath a cloudy rainy sky, smoking in an alley, discarding the
|
||||
cigarette, walking and turning through a separate city map, then meeting the
|
||||
guards and choosing a local profile before entering the walkable hall.
|
||||
The sequence resets on every launch; the intro advances automatically.
|
||||
Enter/Space advances one startup page; Escape skips to the opening scene.
|
||||
After profile admission and entry into the hall, a centred cyberpunk briefing
|
||||
unfolds from a point and types version 4.0, game information and controls.
|
||||
The jukebox starts on entry at 50% volume, with normal positional attenuation.
|
||||
Enter completes the text, then closes it; Escape closes immediately. Movement
|
||||
is blocked while the briefing is open. See [startup and briefing](docs/STARTUP_PRESENTATION.md).
|
||||
Sidorovich operates the native
|
||||
card-back shop. New character sheets contain 32 guard poses and 24 merchant
|
||||
poses, with separate actions rather than a single repeated idle animation.
|
||||
|
|
|
|||
BIN
bin/poker.exe
BIN
bin/poker.exe
Binary file not shown.
|
|
@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
|
|||
{
|
||||
static class Program
|
||||
{
|
||||
const string ExpectedPokerSha256 = "2357F5876DA47FD6CD7FAE7216C71FCEDE6CCA79C1591E2BA6CFD2F3DB61694E";
|
||||
const string ExpectedPokerSha256 = "3D0237E09CAED350548457BFAB24C4D67D6FAFC3D689C2D87C8BE360B64EC0AF";
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
|
|
|
|||
Binary file not shown.
16
casino3d.inc
16
casino3d.inc
|
|
@ -150,6 +150,8 @@ include src_graphics\platform_data.inc
|
|||
include src_graphics\casino_actions_data.inc
|
||||
include src_graphics\audio_data.inc
|
||||
include src_graphics\startup_data.inc
|
||||
include src_graphics\presentation_text.inc
|
||||
include src_graphics\presentation_data.inc
|
||||
include src_graphics\card_tables_data.inc
|
||||
include src_graphics\native_durak_data.inc
|
||||
include src_graphics\scene_animation_data.inc
|
||||
|
|
@ -4497,6 +4499,8 @@ unlock_done:
|
|||
UnlockMouse3D ENDP
|
||||
|
||||
UpdateMouseLockState PROC
|
||||
cmp welcomeActive3D,0
|
||||
jne unlock_it
|
||||
cmp engineMode,MODE_HELL_FPS
|
||||
je lock_interior_check
|
||||
cmp engineMode,MODE_STAIRS
|
||||
|
|
@ -5241,6 +5245,7 @@ include src_graphics\audio_code.inc
|
|||
include src_graphics\dice_animation.inc
|
||||
include src_graphics\russian_roulette.inc
|
||||
include src_graphics\startup_code.inc
|
||||
include src_graphics\presentation_code.inc
|
||||
include src_graphics\card_table_assets.inc
|
||||
include src_graphics\poker_evaluator.inc
|
||||
include src_graphics\native_poker.inc
|
||||
|
|
@ -5341,6 +5346,11 @@ engine_observer_skip:
|
|||
invoke Pressed3D, 1Bh
|
||||
test eax, eax
|
||||
jz engine_tick
|
||||
cmp welcomeActive3D,0
|
||||
je @F
|
||||
call CloseWelcome3D
|
||||
jmp engine_tick
|
||||
@@:
|
||||
cmp engineMode,MODE_HELL_FPS
|
||||
jne @F
|
||||
call LeaveHellFPS3D
|
||||
|
|
@ -5441,6 +5451,12 @@ check_walk_mode:
|
|||
@@:
|
||||
cmp engineMode, MODE_WALK
|
||||
jne check_slots_mode
|
||||
cmp welcomeActive3D,0
|
||||
je welcome_walk_ready
|
||||
call HandleWelcome3D
|
||||
call RenderWelcomeScene3D
|
||||
jmp present_frame
|
||||
welcome_walk_ready:
|
||||
cmp brActive3D,0
|
||||
jne engine_backrooms
|
||||
call UpdateHall3D
|
||||
|
|
|
|||
BIN
dist/Assembler Casino.exe
vendored
BIN
dist/Assembler Casino.exe
vendored
Binary file not shown.
84
docs/STARTUP_PRESENTATION.md
Normal file
84
docs/STARTUP_PRESENTATION.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Startup presentation and hall briefing
|
||||
|
||||
All runtime presentation, text, timing, input and drawing are MASM32. No browser,
|
||||
video player, Python runtime or additional graphics dependency is introduced.
|
||||
|
||||
## Startup order
|
||||
|
||||
1. Existing Eblan Games animated logo (7 seconds; its F2 variant is retained).
|
||||
2. Russian AI-use disclosure (10 seconds): artificial intelligence technologies
|
||||
were used to create some game textures; third-party resources retain their
|
||||
authors and licences.
|
||||
3. Code fact (11 seconds): side-by-side editor-style C++ and MASM panels for
|
||||
clearing the same 640×480 framebuffer. C++ is explicitly an illustrative
|
||||
equivalent, not a second game runtime. MASM shows the actual clear operation
|
||||
from `src_graphics/startup_sequence.inc`.
|
||||
4. Existing first-person city opening, guards and profile selection.
|
||||
|
||||
Pages advance automatically. **Enter/Space** advances one page, clearing held
|
||||
input so it cannot skip several screens at once. **Escape** skips the startup
|
||||
pages and begins the existing city scene. Starting again resets all pages.
|
||||
Existing missing-logo fallback still works; the two new pages need no art files.
|
||||
|
||||
## Post-entry briefing
|
||||
|
||||
After a guest/existing/new profile is admitted and the player enters the hall,
|
||||
the jukebox starts **Bolden - Art Deco** on repeat at **50% user volume** (5/10).
|
||||
Existing distance/floor attenuation still applies, so it sounds quieter away
|
||||
from the cabinet. Volume is applied before playback, avoiding a full-volume
|
||||
startup burst. No jukebox music plays during startup/profile selection.
|
||||
Closing the briefing does not restart the track or reset volume; the existing
|
||||
jukebox controls and +/- volume keys remain available after closing it.
|
||||
|
||||
At the same entry point, the centred panel deploys from a point: horizontal
|
||||
expansion for 200 ms, then
|
||||
vertical ease-out for 400 ms. Cyan/magenta rails and a scanning marker frame the
|
||||
dark panel. The hall is rendered by the normal ASM renderer and dimmed beneath
|
||||
it; movement, interactions and camera input do not run until the modal closes.
|
||||
|
||||
After deployment, Russian text types at one character per 18 ms with a blinking
|
||||
cursor: version **4.0 / Windows x86**, engine, activities, controls
|
||||
and profile persistence. Version follows the existing 4.0 build identity; this
|
||||
change does not introduce a new release/version bump or alter save formats.
|
||||
|
||||
- **Enter/Space**: complete the text; press again to close.
|
||||
- **Escape**: close immediately, without opening pause.
|
||||
- **Enter game** button: same complete/close behaviour, with mouse input.
|
||||
- No automatic dismissal; the information remains readable.
|
||||
- Shown once per admission on each launch, not on returning from tables or Hell.
|
||||
|
||||
The core remains assembly-only; the pre-existing C# packaging tools and the
|
||||
approved C++ fortune inference worker are separate from the game engine.
|
||||
|
||||
## Copy and implementation
|
||||
|
||||
- `tools/generators/make_presentation_text.py`: editable Russian copy; emits
|
||||
numeric CP1251 declarations into `src_graphics/presentation_text.inc`.
|
||||
- `src_graphics/presentation_data.inc`: timings, page/briefing state and API.
|
||||
- `src_graphics/presentation_code.inc`: native bitmap text, code windows,
|
||||
unfolding modal and typewriter.
|
||||
- `src_graphics/startup_code.inc`: three-page sequencing.
|
||||
- `src_graphics/arrival_scene.inc`: briefing starts after admission/entry only.
|
||||
- `casino3d.inc`: modal input gate, mouse unlock and Escape handling.
|
||||
|
||||
## Verification
|
||||
|
||||
```powershell
|
||||
python tools/generators/make_presentation_text.py
|
||||
python tools/tests/check_presentation.py
|
||||
python tools/tests/check_presentation.py --window
|
||||
```
|
||||
|
||||
Tests compile the production procedures with an isolated ASM entry point. They
|
||||
check automatic and key transitions, restart/reset, existing city-scene handoff,
|
||||
guest admission, delayed modal trigger, deployment, typing and manual completion,
|
||||
button/close input cleanup, and unchanged bank, RNG and player save files.
|
||||
They also query actual MCI playback and gain after entry, verify silence before
|
||||
admission, retain a changed volume and playback position across modal closure,
|
||||
then exercise manual track selection and Stop. MCI gain assertions allow device
|
||||
rounding (four units out of 1000); both success and failure close `jboxmusic`.
|
||||
`--window` also presents 150 real Win32 frames.
|
||||
|
||||
Outputs: `build/scratch/presentation-00..11.png`, `presentation-contact.png` and
|
||||
`presentation-window.png`. Existing action/arrival tests also check the revised
|
||||
startup order and the new admission trigger.
|
||||
|
|
@ -2072,7 +2072,14 @@ ENDIF
|
|||
IFDEF HELL_FPS_TEST
|
||||
include tools/tests/hell_fps_test.inc
|
||||
ENDIF
|
||||
IFDEF PRESENTATION_TEST
|
||||
include tools/tests/presentation_test.inc
|
||||
ENDIF
|
||||
main PROC
|
||||
IFDEF PRESENTATION_TEST
|
||||
call RunPresentationTest3D
|
||||
invoke ExitProcess,eax
|
||||
ENDIF
|
||||
IFDEF HELL_FPS_TEST
|
||||
IFDEF HELL_FPS_WINDOW_TEST
|
||||
call RunHellFPSWindowTest
|
||||
|
|
|
|||
BIN
poker.exe
BIN
poker.exe
Binary file not shown.
BIN
poker.obj
BIN
poker.obj
Binary file not shown.
|
|
@ -128,6 +128,10 @@ arrival_entry:
|
|||
mov interactTargetId, 0
|
||||
mov interactPromptPtr, 0
|
||||
call ClearKeys3D
|
||||
call BeginWelcome3D
|
||||
; Start only after admission, with the normal positional attenuation.
|
||||
mov jboxUserVolume, 5
|
||||
invoke PlayJukeboxTrack3D, 0
|
||||
ret
|
||||
arrival_profile_input:
|
||||
invoke Pressed3D, 26h
|
||||
|
|
@ -309,4 +313,3 @@ entry_rows_done:
|
|||
invoke DrawString8x8, 223, 207, OFFSET entryConfirm3D, 00FFFFFFh
|
||||
ret
|
||||
DrawEntryProfileRows3D ENDP
|
||||
|
||||
|
|
|
|||
|
|
@ -118,11 +118,18 @@ PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD
|
|||
test eax, eax
|
||||
jz jbox_play_stop_selected
|
||||
invoke mciSendStringA, eax, 0, 0, 0
|
||||
invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0
|
||||
test eax, eax
|
||||
jnz jbox_play_stop_selected
|
||||
mov jboxCurrentTrack, ebx
|
||||
mov jboxIsPlaying, 1
|
||||
mov jboxEffectiveVolume, -1
|
||||
; Apply volume before play: never start a newly opened device at full gain.
|
||||
call UpdateJukeboxAudio3D
|
||||
invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0
|
||||
test eax, eax
|
||||
jz jbox_play_done
|
||||
call StopJukeboxTrack3D
|
||||
jbox_play_done:
|
||||
ret
|
||||
jbox_play_stop_selected:
|
||||
mov jboxCurrentTrack, -1
|
||||
|
|
|
|||
358
src_graphics/presentation_code.inc
Normal file
358
src_graphics/presentation_code.inc
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
; Native-pixel, CP1251 bitmap text. Count includes newlines for typewriter timing.
|
||||
; Unlike DrawString8x8's 320x240 coordinates, this uses the full 640x480 frame.
|
||||
PresentationText3D PROC uses esi edi ebx x:DWORD,y:DWORD,text:DWORD,ink:DWORD,scale:DWORD,limit:DWORD
|
||||
local px:DWORD,py:DWORD,row:DWORD,col:DWORD,bits:DWORD,repeatY:DWORD,repeatX:DWORD
|
||||
mov esi,text
|
||||
mov eax,x
|
||||
mov px,eax
|
||||
mov eax,y
|
||||
mov py,eax
|
||||
pres_text_char:
|
||||
cmp limit,0
|
||||
je pres_text_done
|
||||
movzx eax,BYTE PTR [esi]
|
||||
test eax,eax
|
||||
jz pres_text_done
|
||||
inc esi
|
||||
dec limit
|
||||
cmp eax,10
|
||||
jne pres_text_glyph
|
||||
mov eax,x
|
||||
mov px,eax
|
||||
mov eax,scale
|
||||
imul eax,11
|
||||
add py,eax
|
||||
jmp pres_text_char
|
||||
pres_text_glyph:
|
||||
lea edi,font8x8[eax*8]
|
||||
mov row,0
|
||||
pres_text_row:
|
||||
mov eax,row
|
||||
movzx ebx,BYTE PTR [edi+eax]
|
||||
mov bits,ebx
|
||||
mov col,0
|
||||
pres_text_col:
|
||||
test bits,80h
|
||||
jz pres_text_next_col
|
||||
mov repeatY,0
|
||||
pres_text_repeat_y:
|
||||
mov eax,row
|
||||
imul eax,scale
|
||||
add eax,py
|
||||
add eax,repeatY
|
||||
cmp eax,SCREEN_H
|
||||
jae pres_text_next_y
|
||||
imul eax,SCREEN_W
|
||||
mov ebx,eax
|
||||
mov repeatX,0
|
||||
pres_text_repeat_x:
|
||||
mov eax,col
|
||||
imul eax,scale
|
||||
add eax,px
|
||||
add eax,repeatX
|
||||
cmp eax,SCREEN_W
|
||||
jae pres_text_next_x
|
||||
add eax,ebx
|
||||
mov edx,ink
|
||||
mov screenBuffer[eax*4],edx
|
||||
pres_text_next_x:
|
||||
inc repeatX
|
||||
mov eax,repeatX
|
||||
cmp eax,scale
|
||||
jb pres_text_repeat_x
|
||||
pres_text_next_y:
|
||||
inc repeatY
|
||||
mov eax,repeatY
|
||||
cmp eax,scale
|
||||
jb pres_text_repeat_y
|
||||
pres_text_next_col:
|
||||
shl bits,1
|
||||
inc col
|
||||
cmp col,8
|
||||
jb pres_text_col
|
||||
inc row
|
||||
cmp row,8
|
||||
jb pres_text_row
|
||||
mov eax,scale
|
||||
shl eax,3
|
||||
add px,eax
|
||||
jmp pres_text_char
|
||||
pres_text_done:
|
||||
ret
|
||||
PresentationText3D ENDP
|
||||
|
||||
RenderStartupNotice3D PROC uses ebx esi edi
|
||||
local lineY:DWORD,ink:DWORD
|
||||
invoke DrawBox,0,0,320,240,00060912h
|
||||
; Quiet terminal grid, fixed geometry: never consumes gameplay RNG.
|
||||
mov ebx,12
|
||||
pres_notice_grid:
|
||||
invoke DrawBox,0,ebx,320,1,000C1722h
|
||||
add ebx,16
|
||||
cmp ebx,240
|
||||
jb pres_notice_grid
|
||||
invoke DrawBox,20,26,3,22,00FFCE54h
|
||||
invoke DrawBox,26,26,274,1,002C6875h
|
||||
invoke DrawBox,20,212,280,1,002C6875h
|
||||
cmp startupPage3D,STARTUP_AI
|
||||
jne pres_notice_code
|
||||
invoke PresentationText3D,56,60,OFFSET presAITag,004FE5E2h,1,-1
|
||||
invoke PresentationText3D,56,88,OFFSET presAIHeader,00FFDF82h,2,-1
|
||||
invoke PresentationText3D,40,156,OFFSET presAIBody,00CFDCE4h,2,-1
|
||||
invoke PresentationText3D,56,378,OFFSET presAIFooter,004FE5E2h,1,-1
|
||||
jmp pres_notice_footer
|
||||
pres_notice_code:
|
||||
invoke PresentationText3D,56,64,OFFSET presCodeHeader,00FFDF82h,2,-1
|
||||
invoke PresentationText3D,40,112,OFFSET presCodeSub,00CFDCE4h,2,-1
|
||||
invoke DrawBoxBorder,12,80,144,90,00080F1Bh,002D7C92h,1
|
||||
invoke DrawBoxBorder,164,80,144,90,00080F1Bh,003BE8CAh,1
|
||||
invoke DrawBox,13,81,142,14,0017283Ch
|
||||
invoke DrawBox,165,81,142,14,00143435h
|
||||
invoke PresentationText3D,36,170,OFFSET presCodeLeft,00D0DDEBh,1,-1
|
||||
invoke PresentationText3D,340,170,OFFSET presCodeRight,0062F3D3h,1,-1
|
||||
mov esi,0
|
||||
mov lineY,204
|
||||
pres_code_line:
|
||||
mov ink,00C9D9E8h
|
||||
cmp esi,0
|
||||
jne @F
|
||||
mov ink,00657D92h
|
||||
@@:
|
||||
invoke PresentationText3D,36,lineY,presCppLines[esi*4],ink,1,-1
|
||||
invoke PresentationText3D,340,lineY,presAsmLines[esi*4],ink,1,-1
|
||||
add lineY,17
|
||||
inc esi
|
||||
cmp esi,7
|
||||
jb pres_code_line
|
||||
invoke PresentationText3D,24,356,OFFSET presCodeCaption,00FFDF82h,2,-1
|
||||
invoke PresentationText3D,40,386,OFFSET presCodeNote,009FB8C8h,1,-1
|
||||
pres_notice_footer:
|
||||
invoke PresentationText3D,40,448,OFFSET presNext,006F939Fh,1,-1
|
||||
; Fade the notice in, then out at its own page boundary.
|
||||
mov eax,startupElapsed3D
|
||||
cmp eax,300
|
||||
jae pres_notice_fade_out
|
||||
shl eax,8
|
||||
xor edx,edx
|
||||
mov ecx,300
|
||||
div ecx
|
||||
invoke FadeCityFrame3D,eax
|
||||
ret
|
||||
pres_notice_fade_out:
|
||||
mov ecx,startupPage3D
|
||||
mov ebx,startupDurations3D[ecx*4]
|
||||
sub ebx,400
|
||||
cmp eax,ebx
|
||||
jb pres_notice_done
|
||||
sub eax,ebx
|
||||
cmp eax,400
|
||||
jbe @F
|
||||
mov eax,400
|
||||
@@:
|
||||
shl eax,8
|
||||
xor edx,edx
|
||||
mov ecx,400
|
||||
div ecx
|
||||
neg eax
|
||||
add eax,256
|
||||
invoke FadeCityFrame3D,eax
|
||||
pres_notice_done:
|
||||
ret
|
||||
RenderStartupNotice3D ENDP
|
||||
|
||||
BeginWelcome3D PROC
|
||||
mov welcomeActive3D,1
|
||||
mov welcomeElapsed3D,0
|
||||
mov welcomeChars3D,0
|
||||
mov paused3D,0
|
||||
mov interactTargetId,0
|
||||
mov interactPromptPtr,0
|
||||
call ClearKeys3D
|
||||
call UnlockMouse3D
|
||||
ret
|
||||
BeginWelcome3D ENDP
|
||||
|
||||
CloseWelcome3D PROC
|
||||
mov welcomeActive3D,0
|
||||
mov paused3D,0
|
||||
call ClearKeys3D
|
||||
ret
|
||||
CloseWelcome3D ENDP
|
||||
|
||||
HandleWelcome3D PROC
|
||||
cmp welcomeActive3D,0
|
||||
je welcome_handle_done
|
||||
mov eax,deltaTime3D
|
||||
add welcomeElapsed3D,eax
|
||||
cmp welcomeElapsed3D,WELCOME_OPEN_MS
|
||||
jb welcome_handle_done
|
||||
; Once manually completed, do not rewind the typewriter on the next frame.
|
||||
cmp welcomeChars3D,welcomeTextLength3D
|
||||
jae welcome_input
|
||||
mov eax,welcomeElapsed3D
|
||||
sub eax,WELCOME_OPEN_MS
|
||||
xor edx,edx
|
||||
mov ecx,WELCOME_CHAR_MS
|
||||
div ecx
|
||||
cmp eax,welcomeTextLength3D
|
||||
jbe @F
|
||||
mov eax,welcomeTextLength3D
|
||||
@@:
|
||||
mov welcomeChars3D,eax
|
||||
welcome_input:
|
||||
invoke Pressed3D,0Dh
|
||||
test eax,eax
|
||||
jnz welcome_confirm
|
||||
invoke Pressed3D,20h
|
||||
test eax,eax
|
||||
jnz welcome_confirm
|
||||
cmp mouseClick3D,0
|
||||
je welcome_handle_done
|
||||
cmp mouseX3D,212
|
||||
jb welcome_handle_done
|
||||
cmp mouseX3D,292
|
||||
jae welcome_handle_done
|
||||
cmp mouseY3D,184
|
||||
jb welcome_handle_done
|
||||
cmp mouseY3D,198
|
||||
jae welcome_handle_done
|
||||
welcome_confirm:
|
||||
cmp welcomeChars3D,welcomeTextLength3D
|
||||
jae welcome_close
|
||||
mov welcomeChars3D,welcomeTextLength3D
|
||||
call ClearKeys3D
|
||||
ret
|
||||
welcome_close:
|
||||
call CloseWelcome3D
|
||||
welcome_handle_done:
|
||||
ret
|
||||
HandleWelcome3D ENDP
|
||||
|
||||
RenderWelcomeScene3D PROC
|
||||
; Frozen playable hall, not a replacement screenshot or second game runtime.
|
||||
call UpdateCamera3D
|
||||
call RenderCeilingAndFloor
|
||||
call RenderWalls
|
||||
call PrepareCityDepth3D
|
||||
call RenderSprites
|
||||
call RenderHall3D
|
||||
call RenderHUD
|
||||
call RenderWelcome3D
|
||||
ret
|
||||
RenderWelcomeScene3D ENDP
|
||||
|
||||
RenderWelcome3D PROC uses ebx esi edi
|
||||
local halfW:DWORD,halfH:DWORD,left:DWORD,top:DWORD,wide:DWORD,panelH:DWORD
|
||||
local cursorX:DWORD,cursorY:DWORD
|
||||
cmp welcomeActive3D,0
|
||||
je welcome_render_done
|
||||
invoke FadeCityFrame3D,90
|
||||
; Two-stage deployment: point -> horizontal line -> centred panel.
|
||||
mov eax,welcomeElapsed3D
|
||||
cmp eax,200
|
||||
jae welcome_open_vertical
|
||||
imul eax,140
|
||||
xor edx,edx
|
||||
mov ecx,200
|
||||
div ecx
|
||||
inc eax
|
||||
mov halfW,eax
|
||||
mov halfH,1
|
||||
jmp welcome_panel_bounds
|
||||
welcome_open_vertical:
|
||||
mov halfW,140
|
||||
sub eax,200
|
||||
cmp eax,400
|
||||
jbe @F
|
||||
mov eax,400
|
||||
@@:
|
||||
; Ease-out quadratic: 1 - (1-t)^2, with bounded integer operands.
|
||||
neg eax
|
||||
add eax,400
|
||||
imul eax,eax
|
||||
imul eax,81
|
||||
xor edx,edx
|
||||
mov ecx,160000
|
||||
div ecx
|
||||
neg eax
|
||||
add eax,82
|
||||
mov halfH,eax
|
||||
welcome_panel_bounds:
|
||||
mov eax,160
|
||||
sub eax,halfW
|
||||
mov left,eax
|
||||
mov eax,120
|
||||
sub eax,halfH
|
||||
mov top,eax
|
||||
mov eax,halfW
|
||||
shl eax,1
|
||||
mov wide,eax
|
||||
mov eax,halfH
|
||||
shl eax,1
|
||||
mov panelH,eax
|
||||
invoke DrawBoxBorder,left,top,wide,panelH,0008101Bh,003EE7D8h,1
|
||||
mov eax,left
|
||||
add eax,wide
|
||||
sub eax,2
|
||||
invoke DrawBox,eax,top,2,panelH,00DC4B9Bh
|
||||
cmp welcomeElapsed3D,WELCOME_OPEN_MS
|
||||
jb welcome_render_done
|
||||
; Chamfered corners, offset signal rails and a narrow animated scan line.
|
||||
invoke DrawBox,20,38,4,4,0008101Bh
|
||||
invoke DrawBox,296,198,4,4,0008101Bh
|
||||
invoke DrawBox,24,34,46,2,003EE7D8h
|
||||
invoke DrawBox,250,205,46,2,00DC4B9Bh
|
||||
invoke DrawBox,30,70,260,1,00284E62h
|
||||
invoke DrawBox,30,180,260,1,00284E62h
|
||||
mov eax,welcomeElapsed3D
|
||||
shr eax,5
|
||||
xor edx,edx
|
||||
mov ecx,100
|
||||
div ecx
|
||||
add edx,76
|
||||
invoke DrawBox,22,edx,1,2,0058FFF0h
|
||||
invoke PresentationText3D,60,90,OFFSET welcomeHeader,00FFE29Ah,2,-1
|
||||
invoke PresentationText3D,60,120,OFFSET welcomeTag,006C9EB0h,1,-1
|
||||
invoke PresentationText3D,60,154,OFFSET welcomeBody,00CDE9E8h,1,welcomeChars3D
|
||||
cmp welcomeChars3D,welcomeTextLength3D
|
||||
jae welcome_cursor_done
|
||||
test welcomeElapsed3D,100h
|
||||
jnz welcome_cursor_done
|
||||
mov cursorX,60
|
||||
mov cursorY,154
|
||||
mov esi,OFFSET welcomeBody
|
||||
mov edi,welcomeChars3D
|
||||
welcome_cursor_scan:
|
||||
test edi,edi
|
||||
jz welcome_cursor_draw
|
||||
cmp BYTE PTR [esi],10
|
||||
jne welcome_cursor_column
|
||||
mov cursorX,60
|
||||
add cursorY,11
|
||||
jmp welcome_cursor_next
|
||||
welcome_cursor_column:
|
||||
add cursorX,8
|
||||
welcome_cursor_next:
|
||||
inc esi
|
||||
dec edi
|
||||
jmp welcome_cursor_scan
|
||||
welcome_cursor_draw:
|
||||
invoke PresentationText3D,cursorX,cursorY,OFFSET welcomeCursor3D,0058FFF0h,1,1
|
||||
welcome_cursor_done:
|
||||
; Blinking activity indicator; no random flicker and no gameplay RNG changes.
|
||||
mov eax,welcomeElapsed3D
|
||||
test eax,200h
|
||||
jnz @F
|
||||
invoke DrawBox,280,49,7,2,0058FFF0h
|
||||
@@:
|
||||
invoke DrawBoxBorder,212,184,80,14,00172A38h,003EE7D8h,1
|
||||
invoke PresentationText3D,436,376,OFFSET welcomeButton,006EF4DEh,1,-1
|
||||
mov eax,OFFSET welcomeTypeHint
|
||||
cmp welcomeChars3D,welcomeTextLength3D
|
||||
jb @F
|
||||
mov eax,OFFSET welcomeDoneHint
|
||||
@@:
|
||||
invoke PresentationText3D,60,380,eax,008EAFBEh,1,-1
|
||||
welcome_render_done:
|
||||
ret
|
||||
RenderWelcome3D ENDP
|
||||
27
src_graphics/presentation_data.inc
Normal file
27
src_graphics/presentation_data.inc
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
; All timings and presentation state are native ASM; no extra runtime or assets.
|
||||
STARTUP_STUDIO equ 0
|
||||
STARTUP_AI equ 1
|
||||
STARTUP_CODE equ 2
|
||||
WELCOME_OPEN_MS equ 600
|
||||
WELCOME_CHAR_MS equ 18
|
||||
BeginWelcome3D PROTO
|
||||
HandleWelcome3D PROTO
|
||||
CloseWelcome3D PROTO
|
||||
RenderWelcome3D PROTO
|
||||
RenderWelcomeScene3D PROTO
|
||||
PresentationText3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
|
||||
RenderStartupNotice3D PROTO
|
||||
AdvanceStartup3D PROTO
|
||||
.data
|
||||
startupPage3D dd STARTUP_STUDIO
|
||||
startupDurations3D dd 7000,10000,11000
|
||||
welcomeActive3D dd 0
|
||||
welcomeElapsed3D dd 0
|
||||
welcomeChars3D dd 0
|
||||
welcomeCursor3D db '_',0
|
||||
welcomeTextLength3D equ welcomeBodyEnd-welcomeBody-1
|
||||
presCppLines dd OFFSET presCpp0,OFFSET presCpp1,OFFSET presCpp2,OFFSET presCpp3
|
||||
dd OFFSET presCpp4,OFFSET presCpp5,OFFSET presCpp6
|
||||
presAsmLines dd OFFSET presAsm0,OFFSET presAsm1,OFFSET presAsm2,OFFSET presAsm3
|
||||
dd OFFSET presAsm4,OFFSET presAsm5,OFFSET presAsm6
|
||||
.code
|
||||
86
src_graphics/presentation_text.inc
Normal file
86
src_graphics/presentation_text.inc
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
; Generated by tools/generators/make_presentation_text.py. CP1251 bytes.
|
||||
.data
|
||||
presNext db 69,78,84,69,82,32,47,32,83,80,65,67,69,58,32,196,192,203,220,216,197,32,32,32,69,83,67,58,32,207,208,206
|
||||
db 207,211,209,210,200,210,220,0
|
||||
presNextEnd label byte
|
||||
presAIHeader db 206,193,32,200,209,207,206,203,220,199,206,194,192,205,200,200,32,65,73,0
|
||||
presAIHeaderEnd label byte
|
||||
presAITag db 84,82,65,78,83,80,65,82,69,78,67,89,32,78,79,84,73,67,69,32,47,32,48,49,0
|
||||
presAITagEnd label byte
|
||||
presAIBody db 194,32,232,227,240,229,32,229,241,242,252,32,236,224,242,229,240,232,224,235,251,44,32,241,238,231,228,224,237,237,251,229
|
||||
db 10,241,32,239,238,236,238,249,252,254,32,232,241,234,243,241,241,242,226,229,237,237,238,227,238,32,232,237,242,229,235,235
|
||||
db 229,234,242,224,46,10,10,207,240,232,32,241,238,231,228,224,237,232,232,32,247,224,241,242,232,32,242,229,234,241,242,243
|
||||
db 240,32,232,227,240,251,10,232,241,239,238,235,252,231,238,226,224,235,232,241,252,32,242,229,245,237,238,235,238,227,232,232
|
||||
db 10,232,241,234,243,241,241,242,226,229,237,237,238,227,238,32,232,237,242,229,235,235,229,234,242,224,46,10,10,210,224,234
|
||||
db 230,229,32,232,241,239,238,235,252,231,243,254,242,241,255,32,241,242,238,240,238,237,237,232,229,32,240,229,241,243,240,241
|
||||
db 251,46,10,200,245,32,224,226,242,238,240,251,32,232,32,235,232,246,229,237,231,232,232,32,241,238,245,240,224,237,229,237
|
||||
db 251,46,0
|
||||
presAIBodyEnd label byte
|
||||
presAIFooter db 209,206,199,196,192,205,206,32,209,32,65,73,46,32,209,206,193,208,192,205,206,32,194,208,211,215,205,211,46,0
|
||||
presAIFooterEnd label byte
|
||||
presCodeHeader db 192,32,194,219,32,199,205,192,203,200,63,0
|
||||
presCodeHeaderEnd label byte
|
||||
presCodeSub db 200,195,208,206,194,206,201,32,196,194,200,198,206,202,32,205,192,207,200,209,192,205,32,205,192,32,77,65,83,77,51,50
|
||||
db 0
|
||||
presCodeSubEnd label byte
|
||||
presCodeLeft db 67,43,43,32,47,32,207,208,200,204,197,208,0
|
||||
presCodeLeftEnd label byte
|
||||
presCodeRight db 77,65,83,77,32,47,32,202,206,196,32,221,210,206,201,32,200,195,208,219,0
|
||||
presCodeRightEnd label byte
|
||||
presCodeCaption db 206,196,205,192,32,199,192,196,192,215,192,32,45,32,196,194,192,32,223,199,219,202,192,46,0
|
||||
presCodeCaptionEnd label byte
|
||||
presCodeNote db 208,229,237,228,229,240,44,32,243,239,240,224,226,235,229,237,232,229,32,232,32,232,227,240,251,32,45,32,120,56,54,32
|
||||
db 65,83,77,46,10,67,43,43,32,241,235,229,226,224,32,239,238,234,224,231,224,237,32,242,238,235,252,234,238,32,228,235
|
||||
db 255,32,241,240,224,226,237,229,237,232,255,46,0
|
||||
presCodeNoteEnd label byte
|
||||
presCpp0 db 47,47,32,67,108,101,97,114,32,116,104,101,32,102,114,97,109,101,98,117,102,102,101,114,0
|
||||
presCpp0End label byte
|
||||
presCpp1 db 118,111,105,100,32,99,108,101,97,114,70,114,97,109,101,40,41,32,123,0
|
||||
presCpp1End label byte
|
||||
presCpp2 db 32,32,102,111,114,32,40,105,110,116,32,105,32,61,32,48,59,0
|
||||
presCpp2End label byte
|
||||
presCpp3 db 32,32,32,32,32,32,32,105,32,60,32,54,52,48,32,42,32,52,56,48,59,32,43,43,105,41,32,123,0
|
||||
presCpp3End label byte
|
||||
presCpp4 db 32,32,32,32,115,99,114,101,101,110,66,117,102,102,101,114,91,105,93,32,61,32,48,120,48,53,48,56,48,57,59,0
|
||||
presCpp4End label byte
|
||||
presCpp5 db 32,32,125,0
|
||||
presCpp5End label byte
|
||||
presCpp6 db 125,0
|
||||
presCpp6End label byte
|
||||
presAsm0 db 59,32,115,116,97,114,116,117,112,95,115,101,113,117,101,110,99,101,46,105,110,99,0
|
||||
presAsm0End label byte
|
||||
presAsm1 db 108,101,97,32,101,100,105,44,32,115,99,114,101,101,110,66,117,102,102,101,114,0
|
||||
presAsm1End label byte
|
||||
presAsm2 db 109,111,118,32,101,97,120,44,32,48,48,48,53,48,56,48,57,104,0
|
||||
presAsm2End label byte
|
||||
presAsm3 db 109,111,118,32,101,99,120,44,32,83,67,82,69,69,78,95,83,73,90,69,0
|
||||
presAsm3End label byte
|
||||
presAsm4 db 114,101,112,32,115,116,111,115,100,0
|
||||
presAsm4End label byte
|
||||
presAsm5 db 59,32,54,52,48,32,42,32,52,56,48,32,112,105,120,101,108,115,0
|
||||
presAsm5End label byte
|
||||
presAsm6 db 59,32,78,97,116,105,118,101,32,120,56,54,32,47,32,87,105,110,51,50,0
|
||||
presAsm6End label byte
|
||||
welcomeHeader db 65,83,83,69,77,66,76,69,82,32,67,65,83,73,78,79,0
|
||||
welcomeHeaderEnd label byte
|
||||
welcomeTag db 76,79,67,65,76,32,83,69,83,83,73,79,78,32,47,32,83,89,83,84,69,77,32,66,82,73,69,70,73,78,71,0
|
||||
welcomeTagEnd label byte
|
||||
welcomeBody db 62,32,194,197,208,209,200,223,32,52,46,48,32,47,32,87,73,78,68,79,87,83,32,120,56,54,10,10,196,238,225,240
|
||||
db 238,32,239,238,230,224,235,238,226,224,242,252,32,226,32,65,115,115,101,109,98,108,101,114,32,67,97,115,105,110,111,46
|
||||
db 10,202,224,231,232,237,238,32,241,32,241,238,225,241,242,226,229,237,237,251,236,32,50,46,53,68,45,228,226,232,230,234
|
||||
db 238,236,10,237,224,32,224,241,241,229,236,225,235,229,240,229,32,77,65,83,77,51,50,46,10,10,194,237,243,242,240,232
|
||||
db 58,32,234,224,240,242,238,247,237,251,229,32,232,227,240,251,44,32,224,226,242,238,236,224,242,251,44,10,225,238,243,235
|
||||
db 232,237,227,44,32,225,232,235,252,255,240,228,44,32,241,229,234,240,229,242,251,32,232,32,239,238,245,238,228,32,226,32
|
||||
db 192,228,46,10,10,87,65,83,68,32,45,32,228,226,232,230,229,237,232,229,46,32,204,251,248,252,32,45,32,238,241,236
|
||||
db 238,242,240,46,10,69,32,45,32,226,231,224,232,236,238,228,229,233,241,242,226,232,229,46,32,69,83,67,32,45,32,239
|
||||
db 224,243,231,224,46,10,207,240,238,227,240,229,241,241,32,241,238,245,240,224,237,255,229,242,241,255,32,226,32,226,224,248
|
||||
db 229,236,32,239,240,238,244,232,235,229,46,0
|
||||
welcomeBodyEnd label byte
|
||||
welcomeTypeHint db 69,78,84,69,82,58,32,207,206,202,192,199,192,210,220,32,210,197,202,209,210,32,214,197,203,200,202,206,204,0
|
||||
welcomeTypeHintEnd label byte
|
||||
welcomeDoneHint db 69,78,84,69,82,32,47,32,69,83,67,58,32,199,192,202,208,219,210,220,32,200,32,205,192,215,192,210,220,32,200,195
|
||||
db 208,211,0
|
||||
welcomeDoneHintEnd label byte
|
||||
welcomeButton db 194,206,201,210,200,32,194,32,200,195,208,211,0
|
||||
welcomeButtonEnd label byte
|
||||
.code
|
||||
|
|
@ -70,6 +70,8 @@ BeginStartup3D PROC
|
|||
mov interactTargetId, 0
|
||||
mov interactPromptPtr, 0
|
||||
mov startupElapsed3D, 0
|
||||
mov startupPage3D, STARTUP_STUDIO
|
||||
mov welcomeActive3D, 0
|
||||
mov startupVariant3D, 0
|
||||
mov animationClock3D, 0
|
||||
mov rr3d_state, RR_READY
|
||||
|
|
@ -89,12 +91,26 @@ FinishStartup3D PROC
|
|||
ret
|
||||
FinishStartup3D ENDP
|
||||
|
||||
AdvanceStartup3D PROC
|
||||
cmp startupPage3D,STARTUP_CODE
|
||||
jae startup_advance_finish
|
||||
inc startupPage3D
|
||||
mov startupElapsed3D,0
|
||||
call ClearKeys3D
|
||||
ret
|
||||
startup_advance_finish:
|
||||
call FinishStartup3D
|
||||
ret
|
||||
AdvanceStartup3D ENDP
|
||||
|
||||
HandleStartupInput3D PROC
|
||||
mov eax, deltaTime3D
|
||||
add startupElapsed3D, eax
|
||||
invoke Pressed3D, 71h
|
||||
test eax, eax
|
||||
jz @F
|
||||
cmp startupPage3D,STARTUP_STUDIO
|
||||
jne @F
|
||||
xor startupVariant3D, 1
|
||||
mov startupElapsed3D, 0
|
||||
@@:
|
||||
|
|
@ -105,18 +121,22 @@ HandleStartupInput3D PROC
|
|||
test eax, eax
|
||||
jz startup_input_done
|
||||
startup_enter:
|
||||
call FinishStartup3D
|
||||
call AdvanceStartup3D
|
||||
startup_input_done:
|
||||
cmp engineMode, MODE_INTRO
|
||||
jne @F
|
||||
cmp startupElapsed3D, 7000
|
||||
mov ecx,startupPage3D
|
||||
mov eax,startupElapsed3D
|
||||
cmp eax,startupDurations3D[ecx*4]
|
||||
jb @F
|
||||
call FinishStartup3D
|
||||
call AdvanceStartup3D
|
||||
@@:
|
||||
ret
|
||||
HandleStartupInput3D ENDP
|
||||
|
||||
RenderStartup3D PROC
|
||||
cmp startupPage3D,STARTUP_STUDIO
|
||||
jne startup_notice
|
||||
cmp startupVariant3D, 0
|
||||
jne startup_studio_variant
|
||||
cmp startupSequence3D, 0
|
||||
|
|
@ -128,6 +148,9 @@ startup_studio_variant:
|
|||
call RenderStudioStartup3D
|
||||
call FadeStartupOut3D
|
||||
ret
|
||||
startup_notice:
|
||||
call RenderStartupNotice3D
|
||||
ret
|
||||
RenderStartup3D ENDP
|
||||
|
||||
FadeStartupOut3D PROC
|
||||
|
|
|
|||
69
tools/generators/make_presentation_text.py
Normal file
69
tools/generators/make_presentation_text.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
"""Compile Russian presentation copy to CP1251 byte declarations for MASM."""
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TEXT = {
|
||||
'presNext': 'ENTER / SPACE: ДАЛЬШЕ ESC: ПРОПУСТИТЬ',
|
||||
'presAIHeader': 'ОБ ИСПОЛЬЗОВАНИИ AI',
|
||||
'presAITag': 'TRANSPARENCY NOTICE / 01',
|
||||
'presAIBody': 'В игре есть материалы, созданные\n'
|
||||
'с помощью искусственного интеллекта.\n\n'
|
||||
'При создании части текстур игры\n'
|
||||
'использовались технологии\n'
|
||||
'искусственного интеллекта.\n\n'
|
||||
'Также используются сторонние ресурсы.\n'
|
||||
'Их авторы и лицензии сохранены.',
|
||||
'presAIFooter': 'СОЗДАНО С AI. СОБРАНО ВРУЧНУ.',
|
||||
'presCodeHeader': 'А ВЫ ЗНАЛИ?',
|
||||
'presCodeSub': 'ИГРОВОЙ ДВИЖОК НАПИСАН НА MASM32',
|
||||
'presCodeLeft': 'C++ / ПРИМЕР',
|
||||
'presCodeRight': 'MASM / КОД ЭТОЙ ИГРЫ',
|
||||
'presCodeCaption': 'ОДНА ЗАДАЧА — ДВА ЯЗЫКА.',
|
||||
'presCodeNote': 'Рендер, управление и игры — x86 ASM.\n'
|
||||
'C++ слева показан только для сравнения.',
|
||||
'presCpp0': '// Clear the framebuffer',
|
||||
'presCpp1': 'void clearFrame() {',
|
||||
'presCpp2': ' for (int i = 0;',
|
||||
'presCpp3': ' i < 640 * 480; ++i) {',
|
||||
'presCpp4': ' screenBuffer[i] = 0x050809;',
|
||||
'presCpp5': ' }',
|
||||
'presCpp6': '}',
|
||||
'presAsm0': '; startup_sequence.inc',
|
||||
'presAsm1': 'lea edi, screenBuffer',
|
||||
'presAsm2': 'mov eax, 00050809h',
|
||||
'presAsm3': 'mov ecx, SCREEN_SIZE',
|
||||
'presAsm4': 'rep stosd',
|
||||
'presAsm5': '; 640 * 480 pixels',
|
||||
'presAsm6': '; Native x86 / Win32',
|
||||
'welcomeHeader': 'ASSEMBLER CASINO',
|
||||
'welcomeTag': 'LOCAL SESSION / SYSTEM BRIEFING',
|
||||
'welcomeBody': '> ВЕРСИЯ 4.0 / WINDOWS x86\n\n'
|
||||
'Добро пожаловать в Assembler Casino.\n'
|
||||
'Казино с собственным 2.5D-движком\n'
|
||||
'на ассемблере MASM32.\n\n'
|
||||
'Внутри: карточные игры, автоматы,\n'
|
||||
'боулинг, бильярд, секреты и поход в Ад.\n\n'
|
||||
'WASD — движение. Мышь — осмотр.\n'
|
||||
'E — взаимодействие. ESC — пауза.\n'
|
||||
'Прогресс сохраняется в вашем профиле.',
|
||||
'welcomeTypeHint': 'ENTER: ПОКАЗАТЬ ТЕКСТ ЦЕЛИКОМ',
|
||||
'welcomeDoneHint': 'ENTER / ESC: ЗАКРЫТЬ И НАЧАТЬ ИГРУ',
|
||||
'welcomeButton': 'ВОЙТИ В ИГРУ',
|
||||
}
|
||||
TEXT = {name:text.replace('\u2014','-') for name,text in TEXT.items()}
|
||||
|
||||
|
||||
def main():
|
||||
lines = ['; Generated by tools/generators/make_presentation_text.py. CP1251 bytes.', '.data']
|
||||
for name, text in TEXT.items():
|
||||
data = text.encode('cp1251') + b'\0'
|
||||
for offset in range(0,len(data),32):
|
||||
prefix = f'{name} db ' if offset == 0 else ' db '
|
||||
lines.append(prefix + ','.join(map(str, data[offset:offset+32])))
|
||||
lines.append(f'{name}End label byte')
|
||||
lines.append('.code')
|
||||
(ROOT/'src_graphics/presentation_text.inc').write_bytes(('\n'.join(lines)+'\n').encode('ascii'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -202,6 +202,14 @@ RunArrivalTest3D PROC uses ebx esi edi
|
|||
mov startupElapsed3D, 6990
|
||||
mov deltaTime3D, 16
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_AI
|
||||
jne arrival_test_fail
|
||||
mov startupElapsed3D,10000
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_CODE
|
||||
jne arrival_test_fail
|
||||
mov startupElapsed3D,11000
|
||||
call HandleStartupInput3D
|
||||
cmp engineMode, MODE_ARRIVAL
|
||||
jne arrival_test_fail
|
||||
cmp cityPhase3D, CITY_BLACK
|
||||
|
|
@ -595,6 +603,9 @@ arrival_admit_sequence:
|
|||
call HandleArrival3D
|
||||
cmp engineMode, MODE_WALK
|
||||
jne arrival_test_fail
|
||||
cmp welcomeActive3D,1
|
||||
jne arrival_test_fail
|
||||
call CloseWelcome3D
|
||||
cmp playerY, (27*FP_1+FP_HALF)
|
||||
jne arrival_test_fail
|
||||
invoke CanWalk3D, playerX, playerY
|
||||
|
|
|
|||
|
|
@ -231,6 +231,14 @@ action_studio_snapshots:
|
|||
mov keys3D[57h], 1
|
||||
mov pressedKeys3D[45h], 1
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_AI
|
||||
jne action_fail_15
|
||||
mov pressedKeys3D[0Dh],1
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_CODE
|
||||
jne action_fail_15
|
||||
mov pressedKeys3D[0Dh],1
|
||||
call HandleStartupInput3D
|
||||
cmp engineMode, MODE_ARRIVAL
|
||||
jne action_fail_15
|
||||
cmp keys3D[57h], 0
|
||||
|
|
@ -256,6 +264,9 @@ action_studio_snapshots:
|
|||
jz action_fail_15
|
||||
mov pressedKeys3D[20h], 1
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_AI
|
||||
jne action_fail_15
|
||||
call FinishStartup3D
|
||||
cmp engineMode, MODE_ARRIVAL
|
||||
jne action_fail_15
|
||||
cmp rngSeed3D, 12345678h
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ def test_code_structure():
|
|||
assert "call RenderJukeboxVolumeHUD" in casino_inc
|
||||
assert "jboxUserVolume dd" in data_inc
|
||||
assert "setaudio jboxmusic volume to" in data_inc
|
||||
play_proc = code_inc.split("PlayJukeboxTrack3D PROC", 1)[1].split("PlayJukeboxTrack3D ENDP", 1)[0]
|
||||
assert play_proc.index("call UpdateJukeboxAudio3D") < play_proc.index("OFFSET jboxPlayCmd"), "Set gain before playback"
|
||||
arrival_inc = (ROOT / "src_graphics/arrival_scene.inc").read_bytes().decode("cp1251")
|
||||
entry = arrival_inc.split("arrival_entry:", 1)[1].split("arrival_profile_input:", 1)[0]
|
||||
assert entry.index("mov engineMode, MODE_WALK") < entry.index("mov jboxUserVolume, 5")
|
||||
assert "invoke PlayJukeboxTrack3D, 0" in entry
|
||||
|
||||
assert "call LoadJackboxArt3D" in world_inc
|
||||
assert "call FreeJackboxArt3D" in world_inc
|
||||
|
|
|
|||
81
tools/tests/check_presentation.py
Normal file
81
tools/tests/check_presentation.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
"""Compile and verify real MASM startup pages and the post-admission briefing."""
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
from PIL import Image, ImageChops, ImageDraw
|
||||
from check_artifacts import toolchain
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
OUT = ROOT/'build/scratch'
|
||||
sys.path.insert(0,str(ROOT/'tools/generators'))
|
||||
from make_presentation_text import TEXT
|
||||
|
||||
|
||||
def snapshot_runtime():
|
||||
return {p:hashlib.sha256(p.read_bytes()).digest()
|
||||
for p in (ROOT/'runtime').rglob('*') if p.is_file()}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--window',action='store_true')
|
||||
args = parser.parse_args()
|
||||
OUT.mkdir(parents=True,exist_ok=True)
|
||||
# Prevent silent clipping of the Russian copy or a missing font character.
|
||||
for name,text in TEXT.items():
|
||||
assert all(len(line)<=39 for line in text.splitlines()),name
|
||||
assert b'\x97' not in text.encode('cp1251'),name
|
||||
before = snapshot_runtime()
|
||||
ml,link = toolchain()
|
||||
sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib')
|
||||
lib = next(p/'um/x86' for p in sorted(sdk.iterdir(),reverse=True)
|
||||
if (p/'um/x86/kernel32.lib').exists())
|
||||
commands = [[ml,'/c','/coff','/Cp','/nologo','/I.','/DPRESENTATION_TEST=1',
|
||||
'/Fobuild/scratch/presentation-test.obj','poker.asm'],
|
||||
[link,'/nologo','/SUBSYSTEM:CONSOLE','/LIBPATH:'+str(lib),
|
||||
'/OUT:build/scratch/presentation-test.exe','build/scratch/presentation-test.obj',
|
||||
'kernel32.lib','winmm.lib','user32.lib','ws2_32.lib','gdi32.lib']]
|
||||
if args.window:
|
||||
commands[0].insert(1,'/DPRESENTATION_WINDOW_TEST=1')
|
||||
try:
|
||||
for command in commands:
|
||||
subprocess.run(list(map(str,command)),cwd=ROOT,check=True,timeout=120)
|
||||
result = subprocess.run([str(OUT/'presentation-test.exe')],cwd=ROOT,timeout=120)
|
||||
assert result.returncode == 0,f'Native presentation check failed: {result.returncode:#x}'
|
||||
finally:
|
||||
assert before == snapshot_runtime(),'Test changed player saves'
|
||||
frames=[]
|
||||
for index in range(12):
|
||||
raw = (OUT/f'presentation-{index:02}.bgra').read_bytes()
|
||||
assert len(raw)==640*480*4
|
||||
image=Image.frombytes('RGBA',(640,480),raw,'raw','BGRA').convert('RGB')
|
||||
image.save(OUT/f'presentation-{index:02}.png')
|
||||
frames.append(image)
|
||||
assert len({im.tobytes() for im in frames})==len(frames),'Frozen/repeated presentation'
|
||||
for index in (1,2):
|
||||
assert len(frames[index].getcolors(640*480))>5,'Missing native text'
|
||||
# Opening starts at screen centre and expands horizontally, then vertically.
|
||||
for index in (3,4,5,6,7):
|
||||
image=frames[index]
|
||||
assert image.getpixel((320,240)) in ((62,231,216),(8,16,27),(220,75,155)),index
|
||||
assert ImageChops.difference(frames[9],frames[11]).getbbox(),'Typewriter did not advance'
|
||||
sheet=Image.new('RGB',(3*640,4*504),(8,12,20))
|
||||
draw=ImageDraw.Draw(sheet)
|
||||
for index,image in enumerate(frames):
|
||||
x,y=index%3*640,index//3*504
|
||||
sheet.paste(image,(x,y))
|
||||
draw.text((x+12,y+484),f'Native ASM / {index:02}',fill='white')
|
||||
sheet.save(OUT/'presentation-contact.png')
|
||||
if args.window:
|
||||
image=Image.frombytes('RGBA',(640,480),(OUT/'presentation-window.bgra').read_bytes(),'raw','BGRA')
|
||||
image.convert('RGB').save(OUT/'presentation-window.png')
|
||||
print('PASS: ordered startup, automatic/key/skip transitions, guest admission, '
|
||||
'centred deployment, typewriter, input cleanup, 50% entry music, '
|
||||
'MCI playback/volume, no modal restart, manual track/stop, unchanged saves/RNG/bank; '
|
||||
'12 native captures'+('; 150 Win32 frames' if args.window else ''))
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
368
tools/tests/presentation_test.inc
Normal file
368
tools/tests/presentation_test.inc
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
; Exercise the shipping ASM startup, admission, overlay and input procedures.
|
||||
.data
|
||||
presTestStage dd 1
|
||||
presTestIo dd 0
|
||||
presTestPath db 'build\scratch\presentation-00.bgra',0
|
||||
presTestWindow db 'build\scratch\presentation-window.bgra',0
|
||||
presTestTimes dd 0,100,200,300,450,600,1200,2400,10000
|
||||
presTestX dd 0
|
||||
presTestY dd 0
|
||||
presTestAngle dd 0
|
||||
presTestBank dd 0
|
||||
presTestRng dd 0
|
||||
presTestMusicMode db 'status jboxmusic mode',0
|
||||
presTestMusicVolume db 'status jboxmusic volume',0
|
||||
presTestMusicPosition db 'status jboxmusic position',0
|
||||
presTestMusicSeek db 'seek jboxmusic to 5000',0
|
||||
presTestMusicReply db 64 dup(0)
|
||||
.code
|
||||
PresTestMusicNumber PROC uses esi ebx command:DWORD
|
||||
invoke mciSendStringA,command,OFFSET presTestMusicReply,64,0
|
||||
test eax,eax
|
||||
jnz pres_music_number_failed
|
||||
xor eax,eax
|
||||
lea esi,presTestMusicReply
|
||||
pres_music_number_digit:
|
||||
movzx ebx,BYTE PTR [esi]
|
||||
test ebx,ebx
|
||||
jz pres_music_number_done
|
||||
sub ebx,'0'
|
||||
cmp ebx,9
|
||||
ja pres_music_number_failed
|
||||
imul eax,10
|
||||
add eax,ebx
|
||||
inc esi
|
||||
jmp pres_music_number_digit
|
||||
pres_music_number_failed:
|
||||
mov eax,-1
|
||||
pres_music_number_done:
|
||||
ret
|
||||
PresTestMusicNumber ENDP
|
||||
|
||||
PresTestCapture PROC uses ebx path:DWORD
|
||||
invoke CreateFileA,path,40000000h,0,0,2,80h,0
|
||||
cmp eax,-1
|
||||
je pres_capture_failed
|
||||
mov ebx,eax
|
||||
invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,OFFSET presTestIo,0
|
||||
push eax
|
||||
invoke CloseHandle,ebx
|
||||
pop eax
|
||||
ret
|
||||
pres_capture_failed:
|
||||
xor eax,eax
|
||||
ret
|
||||
PresTestCapture ENDP
|
||||
|
||||
RunPresentationTest3D PROC uses esi edi ebx
|
||||
call InitPaths3D
|
||||
call InitHall3D
|
||||
call LoadTextures3D
|
||||
call LoadHallAssets3D
|
||||
call InitializeCasinoSprites3D
|
||||
mov rngSeed3D,12345678h
|
||||
mov bankroll,12345
|
||||
mov keys3D[57h],1
|
||||
call BeginStartup3D
|
||||
cmp startupPage3D,STARTUP_STUDIO
|
||||
jne pres_test_failed
|
||||
cmp keys3D[57h],0
|
||||
jne pres_test_failed
|
||||
mov startupElapsed3D,2500
|
||||
call RenderStartup3D
|
||||
invoke PresTestCapture,OFFSET presTestPath
|
||||
mov presTestStage,2
|
||||
; No early transition. At the boundary, studio -> AI (not arrival).
|
||||
mov startupElapsed3D,6999
|
||||
mov deltaTime3D,0
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_STUDIO
|
||||
jne pres_test_failed
|
||||
mov deltaTime3D,1
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_AI
|
||||
jne pres_test_failed
|
||||
cmp startupElapsed3D,0
|
||||
jne pres_test_failed
|
||||
cmp engineMode,MODE_INTRO
|
||||
jne pres_test_failed
|
||||
mov startupElapsed3D,1000
|
||||
call RenderStartup3D
|
||||
mov presTestPath[28],'1'
|
||||
invoke PresTestCapture,OFFSET presTestPath
|
||||
; Enter skips only this page, and held movement keys cannot leak through.
|
||||
mov keys3D[57h],1
|
||||
mov pressedKeys3D[0Dh],1
|
||||
mov deltaTime3D,0
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_CODE
|
||||
jne pres_test_failed
|
||||
cmp keys3D[57h],0
|
||||
jne pres_test_failed
|
||||
mov startupElapsed3D,1500
|
||||
call RenderStartup3D
|
||||
mov presTestPath[28],'2'
|
||||
invoke PresTestCapture,OFFSET presTestPath
|
||||
mov presTestStage,3
|
||||
mov startupElapsed3D,11000
|
||||
call HandleStartupInput3D
|
||||
cmp engineMode,MODE_ARRIVAL
|
||||
jne pres_test_failed
|
||||
cmp arrivalState3D,ARRIVAL_WALK
|
||||
jne pres_test_failed
|
||||
cmp cityPhase3D,CITY_BLACK
|
||||
jne pres_test_failed
|
||||
cmp welcomeActive3D,0
|
||||
jne pres_test_failed
|
||||
cmp jboxIsPlaying,0
|
||||
jne pres_test_failed
|
||||
cmp startupPixels3D,0
|
||||
jne pres_test_failed
|
||||
cmp startupSequence3D,0
|
||||
jne pres_test_failed
|
||||
; Full automatic order, followed by a fresh-launch reset and Escape path.
|
||||
call BeginStartup3D
|
||||
mov deltaTime3D,7000
|
||||
call HandleStartupInput3D
|
||||
mov deltaTime3D,10000
|
||||
call HandleStartupInput3D
|
||||
cmp startupPage3D,STARTUP_CODE
|
||||
jne pres_test_failed
|
||||
mov deltaTime3D,11000
|
||||
call HandleStartupInput3D
|
||||
cmp engineMode,MODE_ARRIVAL
|
||||
jne pres_test_failed
|
||||
call BeginStartup3D
|
||||
cmp startupPage3D,STARTUP_STUDIO
|
||||
jne pres_test_failed
|
||||
call FinishStartup3D
|
||||
cmp engineMode,MODE_ARRIVAL
|
||||
jne pres_test_failed
|
||||
mov presTestStage,4
|
||||
; Guest profile -> admission -> hall triggers the modal only after entry.
|
||||
mov arrivalState3D,ARRIVAL_PROFILE
|
||||
mov arrivalSelected3D,0
|
||||
mov deltaTime3D,0
|
||||
mov pressedKeys3D[0Dh],1
|
||||
call HandleArrival3D
|
||||
cmp arrivalConfirmed3D,1
|
||||
jne pres_test_failed
|
||||
cmp welcomeActive3D,0
|
||||
jne pres_test_failed
|
||||
cmp jboxIsPlaying,0
|
||||
jne pres_test_failed
|
||||
mov deltaTime3D,1200
|
||||
call HandleArrival3D
|
||||
cmp arrivalState3D,ARRIVAL_ENTER
|
||||
jne pres_test_failed
|
||||
cmp welcomeActive3D,0
|
||||
jne pres_test_failed
|
||||
cmp jboxIsPlaying,0
|
||||
jne pres_test_failed
|
||||
mov deltaTime3D,1400
|
||||
call HandleArrival3D
|
||||
cmp engineMode,MODE_WALK
|
||||
jne pres_test_failed
|
||||
cmp welcomeActive3D,1
|
||||
jne pres_test_failed
|
||||
cmp welcomeElapsed3D,0
|
||||
jne pres_test_failed
|
||||
mov presTestStage,41
|
||||
cmp jboxIsPlaying,1
|
||||
jne pres_test_failed
|
||||
cmp jboxCurrentTrack,0
|
||||
jne pres_test_failed
|
||||
cmp jboxUserVolume,5
|
||||
jne pres_test_failed
|
||||
mov presTestStage,42
|
||||
invoke mciSendStringA,OFFSET presTestMusicMode,OFFSET presTestMusicReply,64,0
|
||||
test eax,eax
|
||||
jnz pres_test_failed
|
||||
cmp DWORD PTR presTestMusicReply,79616C70h ; "play" in "playing"
|
||||
jne pres_test_failed
|
||||
mov presTestStage,43
|
||||
invoke PresTestMusicNumber,OFFSET presTestMusicVolume
|
||||
cmp eax,-1
|
||||
je pres_test_failed
|
||||
cmp eax,500
|
||||
ja pres_test_failed
|
||||
test eax,eax
|
||||
jz pres_test_failed
|
||||
; Windows MCI rounds through device gain; allow a few units out of 1000.
|
||||
sub eax,jboxEffectiveVolume
|
||||
jns @F
|
||||
neg eax
|
||||
@@:
|
||||
cmp eax,4
|
||||
ja pres_test_failed
|
||||
mov eax,playerX
|
||||
mov presTestX,eax
|
||||
mov eax,playerY
|
||||
mov presTestY,eax
|
||||
mov eax,playerAngle
|
||||
mov presTestAngle,eax
|
||||
mov eax,bankroll
|
||||
mov presTestBank,eax
|
||||
mov eax,rngSeed3D
|
||||
mov presTestRng,eax
|
||||
mov presTestStage,5
|
||||
xor esi,esi
|
||||
pres_test_overlay_frames:
|
||||
mov eax,presTestTimes[esi*4]
|
||||
mov welcomeElapsed3D,eax
|
||||
mov deltaTime3D,0
|
||||
call HandleWelcome3D
|
||||
call RenderWelcomeScene3D
|
||||
mov eax,esi
|
||||
add al,'3'
|
||||
cmp al,'9'
|
||||
jbe @F
|
||||
sub al,10
|
||||
mov presTestPath[27],'1'
|
||||
@@:
|
||||
mov presTestPath[28],al
|
||||
invoke PresTestCapture,OFFSET presTestPath
|
||||
test eax,eax
|
||||
jz pres_test_failed
|
||||
inc esi
|
||||
cmp esi,9
|
||||
jb pres_test_overlay_frames
|
||||
mov eax,presTestX
|
||||
cmp eax,playerX
|
||||
jne pres_test_failed
|
||||
mov eax,presTestY
|
||||
cmp eax,playerY
|
||||
jne pres_test_failed
|
||||
mov eax,presTestAngle
|
||||
cmp eax,playerAngle
|
||||
jne pres_test_failed
|
||||
mov eax,presTestRng
|
||||
cmp eax,rngSeed3D
|
||||
jne pres_test_failed
|
||||
mov eax,presTestBank
|
||||
cmp eax,bankroll
|
||||
jne pres_test_failed
|
||||
mov presTestStage,6
|
||||
; Put playback past zero: closing/reopening the modal must not restart it.
|
||||
invoke mciSendStringA,OFFSET presTestMusicSeek,0,0,0
|
||||
test eax,eax
|
||||
jnz pres_test_failed
|
||||
invoke mciSendStringA,OFFSET jboxPlayCmd,0,0,0
|
||||
test eax,eax
|
||||
jnz pres_test_failed
|
||||
mov pressedKeys3D[0BDh],1
|
||||
call HandleJukeboxKeys3D
|
||||
cmp jboxUserVolume,4
|
||||
jne pres_test_failed
|
||||
; Early input cannot skip the deployment; typing completion is persistent.
|
||||
call BeginWelcome3D
|
||||
mov deltaTime3D,100
|
||||
mov pressedKeys3D[0Dh],1
|
||||
call HandleWelcome3D
|
||||
cmp welcomeActive3D,1
|
||||
jne pres_test_failed
|
||||
cmp welcomeChars3D,0
|
||||
jne pres_test_failed
|
||||
call ClearKeys3D
|
||||
mov deltaTime3D,700
|
||||
call HandleWelcome3D
|
||||
cmp welcomeChars3D,0
|
||||
je pres_test_failed
|
||||
cmp welcomeChars3D,welcomeTextLength3D
|
||||
jae pres_test_failed
|
||||
mov pressedKeys3D[0Dh],1
|
||||
mov keys3D[57h],1
|
||||
call HandleWelcome3D
|
||||
cmp welcomeActive3D,1
|
||||
jne pres_test_failed
|
||||
cmp welcomeChars3D,welcomeTextLength3D
|
||||
jne pres_test_failed
|
||||
cmp keys3D[57h],0
|
||||
jne pres_test_failed
|
||||
mov deltaTime3D,1
|
||||
call HandleWelcome3D
|
||||
cmp welcomeChars3D,welcomeTextLength3D
|
||||
jne pres_test_failed
|
||||
mov pressedKeys3D[0Dh],1
|
||||
call HandleWelcome3D
|
||||
cmp welcomeActive3D,0
|
||||
jne pres_test_failed
|
||||
cmp pressedKeys3D[0Dh],0
|
||||
jne pres_test_failed
|
||||
cmp jboxUserVolume,4
|
||||
jne pres_test_failed
|
||||
invoke PresTestMusicNumber,OFFSET presTestMusicPosition
|
||||
cmp eax,-1
|
||||
je pres_test_failed
|
||||
cmp eax,5000
|
||||
jb pres_test_failed
|
||||
; Escape uses the same clean close; click stays inside the button.
|
||||
call BeginWelcome3D
|
||||
call CloseWelcome3D
|
||||
cmp welcomeActive3D,0
|
||||
jne pres_test_failed
|
||||
cmp paused3D,0
|
||||
jne pres_test_failed
|
||||
call BeginWelcome3D
|
||||
mov welcomeElapsed3D,10000
|
||||
mov mouseX3D,220
|
||||
mov mouseY3D,190
|
||||
mov mouseClick3D,1
|
||||
call HandleWelcome3D
|
||||
cmp welcomeActive3D,0
|
||||
jne pres_test_failed
|
||||
IFDEF PRESENTATION_WINDOW_TEST
|
||||
mov presTestStage,7
|
||||
call Create3DWindow
|
||||
test eax,eax
|
||||
jz pres_test_failed
|
||||
cmp fullscreen3D,0
|
||||
je @F
|
||||
call ToggleFullscreen3D
|
||||
@@:
|
||||
invoke SetWindowPos,h3DWnd,0,80,80,800,640,14h
|
||||
call BeginWelcome3D
|
||||
invoke ShowWindow,h3DWnd,4
|
||||
mov esi,150
|
||||
pres_test_window_frame:
|
||||
invoke PeekMessageA,OFFSET msg3D,0,0,0,PM_REMOVE
|
||||
test eax,eax
|
||||
jz pres_test_window_tick
|
||||
invoke TranslateMessage,OFFSET msg3D
|
||||
invoke DispatchMessageA,OFFSET msg3D
|
||||
jmp pres_test_window_frame
|
||||
pres_test_window_tick:
|
||||
mov deltaTime3D,50
|
||||
call HandleWelcome3D
|
||||
call RenderWelcomeScene3D
|
||||
call Present3D
|
||||
invoke Sleep,17
|
||||
dec esi
|
||||
jnz pres_test_window_frame
|
||||
invoke PresTestCapture,OFFSET presTestWindow
|
||||
call CloseWelcome3D
|
||||
invoke DestroyWindow,h3DWnd
|
||||
ENDIF
|
||||
mov presTestStage,8
|
||||
; Manual track switching and Stop still work after automatic entry playback.
|
||||
invoke PlayJukeboxTrack3D,1
|
||||
cmp jboxIsPlaying,1
|
||||
jne pres_test_failed
|
||||
cmp jboxCurrentTrack,1
|
||||
jne pres_test_failed
|
||||
cmp jboxUserVolume,4
|
||||
jne pres_test_failed
|
||||
call StopJukeboxTrack3D
|
||||
cmp jboxIsPlaying,0
|
||||
jne pres_test_failed
|
||||
cmp jboxCurrentTrack,-1
|
||||
jne pres_test_failed
|
||||
call FreeStartupAssets3D
|
||||
call FreeArrivalAssets3D
|
||||
xor eax,eax
|
||||
ret
|
||||
pres_test_failed:
|
||||
call StopJukeboxTrack3D
|
||||
mov eax,presTestStage
|
||||
ret
|
||||
RunPresentationTest3D ENDP
|
||||
Loading…
Add table
Reference in a new issue