543 lines
15 KiB
NASM
543 lines
15 KiB
NASM
; ==============================================================================
|
|
; EBLAN ENGINE 3D - Standalone Retro Raycaster Engine
|
|
; Pure x86 Assembly (MASM32) | Software 2.5D DIBits Raycaster
|
|
; ==============================================================================
|
|
|
|
.386
|
|
.model flat, stdcall
|
|
option casemap:none
|
|
|
|
; Standard Windows Libraries
|
|
includelib kernel32.lib
|
|
includelib user32.lib
|
|
includelib gdi32.lib
|
|
includelib winmm.lib
|
|
|
|
; Windows API Prototypes
|
|
ExitProcess PROTO :DWORD
|
|
GetModuleHandleA PROTO :DWORD
|
|
GetCommandLineA PROTO
|
|
GetFileSize PROTO :DWORD, :DWORD
|
|
GetForegroundWindow PROTO
|
|
RegisterClassExA PROTO :DWORD
|
|
CreateWindowExA PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
ShowWindow PROTO :DWORD, :DWORD
|
|
UpdateWindow PROTO :DWORD
|
|
GetDC PROTO :DWORD
|
|
ReleaseDC PROTO :DWORD, :DWORD
|
|
DefWindowProcA PROTO :DWORD, :DWORD, :DWORD, :DWORD
|
|
PeekMessageA PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
TranslateMessage PROTO :DWORD
|
|
DispatchMessageA PROTO :DWORD
|
|
PostQuitMessage PROTO :DWORD
|
|
StretchDIBits PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
GetAsyncKeyState PROTO :DWORD
|
|
GetTickCount PROTO
|
|
Sleep PROTO :DWORD
|
|
timeBeginPeriod PROTO :DWORD
|
|
timeEndPeriod PROTO :DWORD
|
|
AdjustWindowRect PROTO :DWORD, :DWORD, :DWORD
|
|
LoadCursorA PROTO :DWORD, :DWORD
|
|
CreateFileA PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
ReadFile PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
WriteFile PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
CloseHandle PROTO :DWORD
|
|
PlaySoundA PROTO :DWORD, :DWORD, :DWORD
|
|
OpenFileMappingA PROTO :DWORD, :DWORD, :DWORD
|
|
MapViewOfFile PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
UnmapViewOfFile PROTO :DWORD
|
|
OpenProcess PROTO :DWORD, :DWORD, :DWORD
|
|
WaitForSingleObject PROTO :DWORD, :DWORD
|
|
|
|
; Structures
|
|
POINT STRUCT
|
|
x dd 0
|
|
y dd 0
|
|
POINT ENDS
|
|
|
|
MSG STRUCT
|
|
hwnd dd 0
|
|
message dd 0
|
|
wParam dd 0
|
|
lParam dd 0
|
|
time dd 0
|
|
pt POINT <>
|
|
MSG ENDS
|
|
|
|
RECT STRUCT
|
|
left dd 0
|
|
top dd 0
|
|
right dd 0
|
|
bottom dd 0
|
|
RECT ENDS
|
|
|
|
WNDCLASSEXA STRUCT
|
|
cbSize dd 0
|
|
style dd 0
|
|
lpfnWndProc dd 0
|
|
cbClsExtra dd 0
|
|
cbWndExtra dd 0
|
|
hInstance dd 0
|
|
hIcon dd 0
|
|
hCursor dd 0
|
|
hbrBackground dd 0
|
|
lpszMenuName dd 0
|
|
lpszClassName dd 0
|
|
hIconSm dd 0
|
|
WNDCLASSEXA ENDS
|
|
|
|
BITMAPINFOHEADER STRUCT
|
|
biSize dd 0
|
|
biWidth dd 0
|
|
biHeight dd 0
|
|
biPlanes dw 0
|
|
biBitCount dw 0
|
|
biCompression dd 0
|
|
biSizeImage dd 0
|
|
biXPelsPerMeter dd 0
|
|
biYPelsPerMeter dd 0
|
|
biClrUsed dd 0
|
|
biClrImportant dd 0
|
|
BITMAPINFOHEADER ENDS
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; Engine Includes
|
|
; ------------------------------------------------------------------------------
|
|
include include/engine_defs.inc
|
|
include include/font8x8.inc
|
|
include include/engine_data.inc
|
|
include include/engine_math.inc
|
|
include include/engine_map.inc
|
|
include include/engine_textures.inc
|
|
include include/engine_input.inc
|
|
include include/engine_physics.inc
|
|
include include/engine_entity.inc
|
|
include include/engine_light.inc
|
|
include include/engine_raycaster.inc
|
|
include include/engine_render2d.inc
|
|
include include/engine_combat.inc
|
|
include include/engine_gameplay.inc
|
|
include include/engine_cutscene.inc
|
|
include include/engine_preview.inc
|
|
|
|
.code
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; Window Procedure Callback
|
|
; ------------------------------------------------------------------------------
|
|
WndProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
|
|
cmp uMsg, 0002h ; WM_DESTROY
|
|
je handle_destroy
|
|
cmp uMsg, 0100h ; WM_KEYDOWN
|
|
je handle_keydown
|
|
|
|
invoke DefWindowProcA, hWnd, uMsg, wParam, lParam
|
|
ret
|
|
|
|
handle_keydown:
|
|
; Key edges and ESC pause menu are handled via PollInput
|
|
invoke DefWindowProcA, hWnd, uMsg, wParam, lParam
|
|
ret
|
|
|
|
handle_destroy:
|
|
invoke PostQuitMessage, 0
|
|
mov isRunning, 0
|
|
xor eax, eax
|
|
ret
|
|
WndProc ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; Engine_Init: Initializes subsystem, window, math tables and level
|
|
; ------------------------------------------------------------------------------
|
|
Engine_Init PROC uses ebx esi edi
|
|
LOCAL wc:WNDCLASSEXA
|
|
LOCAL rc:RECT
|
|
|
|
invoke timeBeginPeriod, 1
|
|
|
|
; 1. Initialize Engine Subsystems
|
|
call InitMathTables
|
|
call GenerateDefaultTextures ; Procedural baseline (always valid)
|
|
call LoadTexturePack ; Optional: override from assets\textures\texturepack.bin
|
|
call InitLightingSystem
|
|
call LoadMapBinary ; Each room commits its own validated lighting
|
|
|
|
; 2. Register Window Class
|
|
invoke GetModuleHandleA, 0
|
|
mov hEngineInstance, eax
|
|
|
|
mov wc.cbSize, sizeof WNDCLASSEXA
|
|
mov wc.style, 0003h ; CS_HREDRAW | CS_VREDRAW
|
|
mov wc.lpfnWndProc, OFFSET WndProc
|
|
mov wc.cbClsExtra, 0
|
|
mov wc.cbWndExtra, 0
|
|
push hEngineInstance
|
|
pop wc.hInstance
|
|
mov wc.hIcon, 0
|
|
invoke LoadCursorA, 0, 32512 ; IDC_ARROW
|
|
mov wc.hCursor, eax
|
|
mov wc.hbrBackground, 0 ; NULL brush (we draw entire frame)
|
|
mov wc.lpszMenuName, 0
|
|
mov wc.lpszClassName, OFFSET szClassName
|
|
mov wc.hIconSm, 0
|
|
|
|
invoke RegisterClassExA, ADDR wc
|
|
|
|
; 3. Adjust Window Client Rect to 1280x960
|
|
mov rc.left, 0
|
|
mov rc.top, 0
|
|
mov rc.right, WINDOW_W
|
|
mov rc.bottom, WINDOW_H
|
|
invoke AdjustWindowRect, ADDR rc, 00CF0000h, 0 ; WS_OVERLAPPEDWINDOW
|
|
|
|
; Calculate dimensions with borders
|
|
mov eax, rc.right
|
|
sub eax, rc.left
|
|
mov ebx, rc.bottom
|
|
sub ebx, rc.top
|
|
|
|
; 4. Create Main Window
|
|
invoke CreateWindowExA, 0, OFFSET szClassName, OFFSET szWindowTitle,\
|
|
00CF0000h, 100, 50, eax, ebx, 0, 0, hEngineInstance, 0
|
|
mov hEngineWnd, eax
|
|
|
|
invoke ShowWindow, hEngineWnd, 1 ; SW_SHOWNORMAL
|
|
invoke UpdateWindow, hEngineWnd
|
|
invoke GetDC, hEngineWnd
|
|
mov hEngineDC, eax
|
|
|
|
invoke GetTickCount
|
|
mov lastTickCount, eax
|
|
mov fpsTimer, eax
|
|
|
|
ret
|
|
Engine_Init ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; Engine_RenderHUD: Draws 2D overlay text, controls, minimap, and FPS
|
|
; ------------------------------------------------------------------------------
|
|
Engine_RenderHUD PROC uses ebx ecx edx
|
|
cmp isCutsceneActive, 0
|
|
je normal_engine_hud
|
|
call RenderCutsceneOverlay
|
|
ret
|
|
normal_engine_hud:
|
|
cmp hudStyle, HUD_STYLE_DEBUG
|
|
jne clean_hud_mode
|
|
|
|
; --- DEBUG / DEVELOPER HUD MODE ---
|
|
; 1. Top HUD Header
|
|
invoke DrawString8x8, 8, 8, OFFSET strEngineName, COLOR_YELLOW
|
|
|
|
; 2. FPS Counter (drawn at top right X=250 so it never overlaps left elements)
|
|
invoke DrawString8x8, 250, 8, OFFSET szFpsLabel, COLOR_CYAN
|
|
invoke DrawNumber8x8, 282, 8, currentFps, COLOR_CYAN
|
|
|
|
; 3. Controls Bar at Bottom (Debug mode only)
|
|
invoke DrawRect, 0, 215, SCREEN_W, 25, 00101010h
|
|
invoke DrawString8x8, 10, 218, OFFSET strControls1, COLOR_WHITE
|
|
invoke DrawString8x8, 10, 228, OFFSET strControls3, COLOR_GREEN
|
|
jmp check_hints
|
|
|
|
clean_hud_mode:
|
|
; --- CLEAN / IMMERSIVE HUD MODE ---
|
|
; In clean mode, no watermarks and no permanent bottom bar.
|
|
; Full 320x240 view is dedicated to the game world.
|
|
|
|
check_hints:
|
|
; 4. Door / Portal Interaction Hint (Cleanly centered above crosshair)
|
|
call CheckDoorHint
|
|
cmp eax, 1
|
|
je show_door_hint
|
|
cmp eax, 2
|
|
je show_portal_hint
|
|
jmp check_minimap
|
|
|
|
show_door_hint:
|
|
invoke DrawString8x8, 116, 140, OFFSET strDoorHint, COLOR_YELLOW
|
|
jmp check_minimap
|
|
|
|
show_portal_hint:
|
|
invoke DrawString8x8, 110, 140, OFFSET strPortalHint, COLOR_CYAN
|
|
|
|
check_minimap:
|
|
cmp mapErrorFrames, 0
|
|
je hud_no_map_error
|
|
invoke DrawString8x8, 44, 164, OFFSET strMapLoadError, COLOR_RED
|
|
hud_no_map_error:
|
|
; 5. Minimap Radar (Toggle with 'M')
|
|
call DrawMinimap
|
|
|
|
; 6. Gameplay HUD Overlays (Floating Toasts, Dialog Modals, Custom HUD)
|
|
call RenderGameHUDOverlays
|
|
|
|
ret
|
|
Engine_RenderHUD ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; Engine_Frame: Updates single tick and presents frame buffer to window
|
|
; ------------------------------------------------------------------------------
|
|
Engine_Frame PROC uses ebx esi edi
|
|
; 1. Input & Physics Update
|
|
cmp mapErrorFrames, 0
|
|
je no_error_tick
|
|
dec mapErrorFrames
|
|
no_error_tick:
|
|
call PollInput
|
|
cmp isGamePaused, 0
|
|
jne @F
|
|
cmp gameDialogActive, 0
|
|
jne skip_movement_in_dialog
|
|
call UpdatePlayer
|
|
call UpdateEntities
|
|
call UpdateCombat
|
|
call UpdateDynamicLights
|
|
skip_movement_in_dialog:
|
|
call Game_OnTick
|
|
@@:
|
|
|
|
; 2. 3D Raycasting & 2D HUD Render
|
|
call Render3DScene
|
|
call Engine_RenderHUD
|
|
cmp isCutsceneActive, 0
|
|
jne @F
|
|
call RenderCombatHUD
|
|
@@:
|
|
call ApplyCutsceneFade
|
|
call RenderPauseMenu
|
|
|
|
; 2b. Screenshot request (F12): dump fresh framebuffer to BMP
|
|
cmp keyScreenshot, 0
|
|
je @F
|
|
mov keyScreenshot, 0
|
|
call SaveScreenshot
|
|
@@:
|
|
|
|
; 2c. Screen Fade effect for sublevel transitions
|
|
call ApplyScreenFade
|
|
|
|
; 3. Present Framebuffer to Screen via StretchDIBits (320x240 -> 1280x960)
|
|
invoke StretchDIBits, hEngineDC,\
|
|
0, 0, WINDOW_W, WINDOW_H,\
|
|
0, 0, SCREEN_W, SCREEN_H,\
|
|
OFFSET screenBuffer, OFFSET bmiEngine,\
|
|
0, 00CC0020h ; DIB_RGB_COLORS, SRCCOPY
|
|
|
|
; 4. Update FPS Counter
|
|
inc fpsCounter
|
|
invoke GetTickCount
|
|
mov edx, eax
|
|
sub edx, fpsTimer
|
|
cmp edx, 1000
|
|
jl frame_done
|
|
|
|
; 1 second elapsed: update currentFps
|
|
mov eax, fpsCounter
|
|
mov currentFps, eax
|
|
mov fpsCounter, 0
|
|
invoke GetTickCount
|
|
mov fpsTimer, eax
|
|
|
|
frame_done:
|
|
; Reset one-frame input triggers
|
|
mov keyForward, 0
|
|
mov keyBackward, 0
|
|
mov keyStrafeLeft, 0
|
|
mov keyStrafeRight, 0
|
|
mov keyTurnLeft, 0
|
|
mov keyTurnRight, 0
|
|
mov keyAction, 0
|
|
mov keyAttack, 0
|
|
mov keyRestart, 0
|
|
ret
|
|
Engine_Frame ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; SaveScreenshot: Dumps screenBuffer to screenshot.bmp (32-bit top-down BMP)
|
|
; ------------------------------------------------------------------------------
|
|
SaveScreenshot PROC uses ebx
|
|
LOCAL hFile:DWORD, bytesWritten:DWORD
|
|
|
|
invoke CreateFileA, OFFSET szScreenshotPath, GENERIC_WRITE, 0, 0,\
|
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
|
|
mov hFile, eax
|
|
cmp eax, INVALID_HANDLE_VALUE
|
|
je ss_done
|
|
|
|
invoke WriteFile, hFile, OFFSET screenshotHeader, 54, ADDR bytesWritten, 0
|
|
invoke WriteFile, hFile, OFFSET screenBuffer, FRAME_BUFFER_SIZE, ADDR bytesWritten, 0
|
|
invoke CloseHandle, hFile
|
|
ss_done:
|
|
ret
|
|
SaveScreenshot ENDP
|
|
|
|
; Optional first argument: a quoted or unquoted starting map path.
|
|
; Assets and linked map basenames remain relative to the working directory.
|
|
ParseStartupMap PROC uses ebx ecx edx esi edi
|
|
invoke GetCommandLineA
|
|
mov esi, eax
|
|
mov startupMapPath, 0
|
|
cmp byte ptr [esi], 34
|
|
jne skip_executable_word
|
|
inc esi
|
|
skip_executable_quote:
|
|
lodsb
|
|
test al, al
|
|
jz check_cmdline_flags
|
|
cmp al, 34
|
|
jne skip_executable_quote
|
|
jmp skip_argument_spaces
|
|
skip_executable_word:
|
|
lodsb
|
|
test al, al
|
|
jz check_cmdline_flags
|
|
cmp al, 32
|
|
jne skip_executable_word
|
|
skip_argument_spaces:
|
|
cmp byte ptr [esi], 32
|
|
je consume_argument_space
|
|
cmp byte ptr [esi], 9
|
|
jne copy_startup_argument
|
|
consume_argument_space:
|
|
inc esi
|
|
jmp skip_argument_spaces
|
|
copy_startup_argument:
|
|
xor ebx, ebx
|
|
cmp byte ptr [esi], 34
|
|
jne startup_argument_loop_init
|
|
mov ebx, 1
|
|
inc esi
|
|
startup_argument_loop_init:
|
|
mov edi, OFFSET startupMapPath
|
|
mov ecx, MAX_PATH_BYTES - 1
|
|
startup_argument_loop:
|
|
lodsb
|
|
test al, al
|
|
jz terminate_startup_arg
|
|
cmp al, 34
|
|
je terminate_startup_arg
|
|
test ebx, ebx
|
|
jnz write_startup_char
|
|
cmp al, 32
|
|
je terminate_startup_arg
|
|
cmp al, 9
|
|
je terminate_startup_arg
|
|
write_startup_char:
|
|
stosb
|
|
dec ecx
|
|
jnz startup_argument_loop
|
|
mov startupMapPath, 0
|
|
jmp check_cmdline_flags
|
|
terminate_startup_arg:
|
|
mov byte ptr [edi], 0
|
|
|
|
; If startupMapPath starts with '-' and not '--preview:', clear it
|
|
mov al, byte ptr [startupMapPath]
|
|
cmp al, '-'
|
|
jne check_cmdline_flags
|
|
mov esi, OFFSET startupMapPath
|
|
mov edi, OFFSET previewPrefix
|
|
mov ecx, 10
|
|
repe cmpsb
|
|
je check_cmdline_flags
|
|
mov byte ptr [startupMapPath], 0
|
|
|
|
check_cmdline_flags:
|
|
invoke GetCommandLineA
|
|
mov ebx, eax
|
|
|
|
invoke CheckCmdFlag, ebx, OFFSET szFlagNoWeapons
|
|
test eax, eax
|
|
jz @F
|
|
mov gameMode, GAME_MODE_ADVENTURE
|
|
mov enableWeapons, 0
|
|
mov playerWeapon, WEAPON_NONE
|
|
mov showCrosshair, 0
|
|
@@:
|
|
invoke CheckCmdFlag, ebx, OFFSET szFlagAdventure
|
|
test eax, eax
|
|
jz @F
|
|
mov gameMode, GAME_MODE_ADVENTURE
|
|
mov enableWeapons, 0
|
|
mov playerWeapon, WEAPON_NONE
|
|
mov showCrosshair, 0
|
|
@@:
|
|
invoke CheckCmdFlag, ebx, OFFSET szFlagAction
|
|
test eax, eax
|
|
jz @F
|
|
mov gameMode, GAME_MODE_ACTION
|
|
mov enableWeapons, 1
|
|
mov playerWeapon, WEAPON_PISTOL
|
|
mov showCrosshair, 1
|
|
@@:
|
|
invoke CheckCmdFlag, ebx, OFFSET szFlagDebugHud
|
|
test eax, eax
|
|
jz @F
|
|
mov hudStyle, HUD_STYLE_DEBUG
|
|
@@:
|
|
invoke CheckCmdFlag, ebx, OFFSET szFlagCleanHud
|
|
test eax, eax
|
|
jz @F
|
|
mov hudStyle, HUD_STYLE_CLEAN
|
|
@@:
|
|
ret
|
|
ParseStartupMap ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; main: Application Entry Point & Game Loop
|
|
; ------------------------------------------------------------------------------
|
|
main PROC
|
|
LOCAL msg:MSG
|
|
LOCAL curTick:DWORD, elapsed:DWORD
|
|
|
|
call LoadProjectSettings
|
|
call ParseStartupMap
|
|
call TryEnginePreview
|
|
call Engine_Init
|
|
|
|
main_loop:
|
|
cmp isRunning, 0
|
|
je exit_loop
|
|
|
|
; Process Window Messages (Non-blocking)
|
|
@@:
|
|
invoke PeekMessageA, ADDR msg, 0, 0, 0, 1 ; PM_REMOVE
|
|
test eax, eax
|
|
jz process_game_tick
|
|
|
|
cmp msg.message, 0012h ; WM_QUIT
|
|
je exit_loop
|
|
|
|
invoke TranslateMessage, ADDR msg
|
|
invoke DispatchMessageA, ADDR msg
|
|
jmp @B
|
|
|
|
process_game_tick:
|
|
; Frame Rate Limiter (~60 FPS)
|
|
invoke GetTickCount
|
|
mov curTick, eax
|
|
sub eax, lastTickCount
|
|
mov elapsed, eax
|
|
|
|
mov eax, targetFrameTimeMs
|
|
cmp elapsed, eax
|
|
jge do_tick
|
|
|
|
invoke Sleep, 1
|
|
jmp main_loop
|
|
|
|
do_tick:
|
|
mov eax, curTick
|
|
mov lastTickCount, eax
|
|
|
|
call Engine_Frame
|
|
jmp main_loop
|
|
|
|
exit_loop:
|
|
call StopCutscene
|
|
invoke ReleaseDC, hEngineWnd, hEngineDC
|
|
invoke timeEndPeriod, 1
|
|
invoke ExitProcess, 0
|
|
main ENDP
|
|
|
|
END main
|