EBLAN_ENGINE/include/engine_map.inc
Игорь Федосеев aa64510def feat: stabilize fixed-tick simulation
Decouple gameplay from presentation and preserve signed movement fractions. Separate cinematic camera ownership with safe body handoff and transactional current-room restarts.

Resample input latches on focus regain, discard modal timing debt, and attach cinematic lighting to the view to match editor preview.

Verify editor 137/137 and ASM 288/288, cinematic traces, RGB bake and IPC framebuffer parity without changing project assets.
2026-10-31 22:31:20 +03:00

1129 lines
29 KiB
PHP

; ==============================================================================
; EBLAN ENGINE - Map System & World Grid
; ==============================================================================
.code
; Forward prototypes for map & sublevel transition procedures
SwitchSublevel PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
LoadSublevelBinary PROTO :DWORD, :DWORD, :DWORD, :DWORD
DoSublevelTransition PROTO
ApplyScreenFade PROTO
ValidateMapFilename PROTO :DWORD
ValidateStagedCutscene PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
CheckPortalTriggers PROTO
InitEntities PROTO
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
BakeMapLighting PROTO
InitLightingSystem PROTO
ValidateLightingData PROTO :DWORD,:DWORD,:DWORD
CommitLightingData PROTO :DWORD,:DWORD,:DWORD,:DWORD
ResetMovementFractions PROTO
CanOccupyPlayer PROTO :DWORD,:DWORD
; ------------------------------------------------------------------------------
; InitMap: Creates a rich test level with rooms, hallways, pillars & doors
; ------------------------------------------------------------------------------
InitMap PROC uses eax ecx edi
call StopCutscene
; Clear map to empty
mov edi, OFFSET gameMap
mov ecx, MAP_SIZE
xor eax, eax
rep stosb
; 1. Build Outer Boundary Walls (Type 1: Brick)
mov ecx, 0
loop_boundary_x:
; Top row (y = 0)
mov byte ptr [gameMap + ecx], CELL_BRICK
; Bottom row (y = 31)
mov byte ptr [gameMap + (31 * MAP_W) + ecx], CELL_BRICK
inc ecx
cmp ecx, MAP_W
jl loop_boundary_x
mov ecx, 0
loop_boundary_y:
; Left column (x = 0)
mov eax, ecx
shl eax, 5 ; eax = ecx * 32
mov byte ptr [gameMap + eax], CELL_BRICK
; Right column (x = 31)
mov byte ptr [gameMap + eax + 31], CELL_BRICK
inc ecx
cmp ecx, MAP_H
jl loop_boundary_y
; 2. Build Internal Room Walls (y = 8, x from 1 to 20, Stone Brick)
mov ecx, 1
loop_wall1:
mov byte ptr [gameMap + (8 * MAP_W) + ecx], CELL_STONE
inc ecx
cmp ecx, 20
jle loop_wall1
; Create Door at (x = 10, y = 8)
mov byte ptr [gameMap + (8 * MAP_W) + 10], CELL_DOOR
; 3. Build Vertical Dividing Wall (x = 20, y from 1 to 24, Wood)
mov ecx, 1
loop_wall2:
cmp ecx, 14 ; Leave opening at y = 14
je @F
mov eax, ecx
shl eax, 5 ; ecx * 32
mov byte ptr [gameMap + eax + 20], CELL_WOOD
@@:
inc ecx
cmp ecx, 24
jle loop_wall2
; 4. Add Pillars in the main courtyard (Type 4: Metal / Type 7: Pillar)
mov byte ptr [gameMap + (4 * MAP_W) + 4], CELL_PILLAR
mov byte ptr [gameMap + (4 * MAP_W) + 7], CELL_PILLAR
mov byte ptr [gameMap + (12 * MAP_W) + 5], CELL_METAL
mov byte ptr [gameMap + (12 * MAP_W) + 15], CELL_METAL
mov byte ptr [gameMap + (18 * MAP_W) + 8], CELL_PILLAR
mov byte ptr [gameMap + (18 * MAP_W) + 12], CELL_PILLAR
; 5. South wing decorative walls
mov byte ptr [gameMap + (24 * MAP_W) + 5], CELL_STONE
mov byte ptr [gameMap + (24 * MAP_W) + 6], CELL_STONE
mov byte ptr [gameMap + (24 * MAP_W) + 7], CELL_DOOR
mov byte ptr [gameMap + (24 * MAP_W) + 8], CELL_STONE
mov byte ptr [gameMap + (24 * MAP_W) + 9], CELL_STONE
; Safe player spawn + clear entities (raw world units)
mov playerX, 160
mov playerY, 160
mov playerAngle, 0
mov currentMapPath, 0
mov roomEntryX, 2
mov roomEntryY, 2
mov roomEntryAngle, 0
call ResetMovementFractions
mov cutsceneCount, 0
mov cutsceneStringCount, 0
mov cutsceneFlags, 0
mov cutscenePending, 0
mov spriteCount, 0
mov transitionCount, 0
mov portalLatchCell, -1
mov mapCeilingColor, COLOR_CEILING
mov mapFloorColor, COLOR_FLOOR
call InitLightingSystem
ret
InitMap ENDP
; ------------------------------------------------------------------------------
; GetMapCell: Returns cell value at grid coordinates (gridX, gridY)
; ------------------------------------------------------------------------------
GetMapCell PROC mapX:DWORD, mapY:DWORD
mov eax, mapX
cmp eax, 0
jl out_of_bounds
cmp eax, MAP_W
jge out_of_bounds
mov edx, mapY
cmp edx, 0
jl out_of_bounds
cmp edx, MAP_H
jge out_of_bounds
shl edx, 5 ; mapY * 32
add edx, eax
movzx eax, byte ptr [gameMap + edx]
ret
out_of_bounds:
mov eax, CELL_BRICK ; Solid wall outside bounds
ret
GetMapCell ENDP
; ------------------------------------------------------------------------------
; SetMapCell: Sets cell value at grid coordinates (gridX, gridY)
; ------------------------------------------------------------------------------
SetMapCell PROC mapX:DWORD, mapY:DWORD, cellVal:DWORD
mov eax, mapX
cmp eax, 0
jl done_set
cmp eax, MAP_W
jge done_set
mov edx, mapY
cmp edx, 0
jl done_set
cmp edx, MAP_H
jge done_set
shl edx, 5
add edx, eax
mov al, byte ptr [cellVal]
mov byte ptr [gameMap + edx], al
done_set:
ret
SetMapCell ENDP
; ------------------------------------------------------------------------------
; IsSolidCell: Returns 1 if cell blocks player and rays, 0 otherwise
; ------------------------------------------------------------------------------
IsSolidCell PROC cellType:DWORD
mov eax, cellType
cmp eax, CELL_EMPTY
je not_solid
cmp eax, CELL_DOOR_OPEN
je not_solid
mov eax, 1 ; Solid wall, closed door, or portal wall
ret
not_solid:
xor eax, eax
ret
IsSolidCell ENDP
; ------------------------------------------------------------------------------
; InteractDoor: Checks if player is facing a door or portal and handles interaction
; ------------------------------------------------------------------------------
InteractDoor PROC uses ebx ecx edx esi edi
LOCAL targetX:DWORD, targetY:DWORD
; Compute point 60 units in front of player
mov eax, playerAngle
and eax, ANGLE_MASK
; dx = cos(angle) * 60
mov ebx, [tableCos + eax*4]
imul ebx, 60
sar ebx, 16
add ebx, playerX
sar ebx, 6 ; divide by CELL_SIZE (64)
mov targetX, ebx
; dy = sin(angle) * 60
mov ecx, [tableSin + eax*4]
imul ecx, 60
sar ecx, 16
add ecx, playerY
sar ecx, 6
mov targetY, ecx
invoke GetMapCell, targetX, targetY
cmp eax, CELL_DOOR
jne check_door_close
; Open the door!
invoke SetMapCell, targetX, targetY, CELL_DOOR_OPEN
invoke ShowGameToast, OFFSET szDoorOpenedToast, 50, COLOR_GREEN
ret
check_door_close:
cmp eax, CELL_DOOR_OPEN
jne check_portal_interact
; Never close a door over any part of the player's collision body.
mov eax, playerX
sub eax, playerRadius
sar eax, CELL_SHIFT
cmp eax, targetX
jg close_clear_door
mov eax, playerX
add eax, playerRadius
sar eax, CELL_SHIFT
cmp eax, targetX
jl close_clear_door
mov eax, playerY
sub eax, playerRadius
sar eax, CELL_SHIFT
cmp eax, targetY
jg close_clear_door
mov eax, playerY
add eax, playerRadius
sar eax, CELL_SHIFT
cmp eax, targetY
jge no_door
close_clear_door:
invoke SetMapCell, targetX, targetY, CELL_DOOR
invoke ShowGameToast, OFFSET szDoorClosedToast, 50, COLOR_YELLOW
ret
check_portal_interact:
; Check if facing a transition trigger or CELL_PORTAL
cmp transitionCount, 0
je no_door
mov esi, OFFSET transitionList
mov edx, 0
portal_find_loop:
cmp edx, transitionCount
jge no_door
movzx eax, [esi].LevelTransition.triggerCellX
cmp eax, targetX
jne portal_find_next
movzx eax, [esi].LevelTransition.triggerCellY
cmp eax, targetY
jne portal_find_next
; Found transition!
movzx eax, [esi].LevelTransition.targetSpawnX
movzx ebx, [esi].LevelTransition.targetSpawnY
movzx ecx, [esi].LevelTransition.targetAngle
movzx edx, [esi].LevelTransition.fadeEffect
lea edi, [esi].LevelTransition.targetMap
invoke SwitchSublevel, edi, eax, ebx, ecx, edx
ret
portal_find_next:
add esi, sizeof LevelTransition
inc edx
jmp portal_find_loop
no_door:
ret
InteractDoor ENDP
; ------------------------------------------------------------------------------
; CheckDoorHint: Returns 1 for Door, 2 for Portal, 0 for none (for HUD hint)
; ------------------------------------------------------------------------------
CheckDoorHint PROC uses ebx ecx edx
LOCAL targetX:DWORD, targetY:DWORD
; Compute point 60 units in front of player
mov eax, playerAngle
and eax, ANGLE_MASK
mov ebx, [tableCos + eax*4]
imul ebx, 60
sar ebx, 16
add ebx, playerX
sar ebx, 6
mov targetX, ebx
mov ecx, [tableSin + eax*4]
imul ecx, 60
sar ecx, 16
add ecx, playerY
sar ecx, 6
mov targetY, ecx
invoke GetMapCell, targetX, targetY
cmp eax, CELL_DOOR
je hint_door
cmp eax, CELL_DOOR_OPEN
je hint_door
cmp eax, CELL_PORTAL
je hint_portal
; Check if target is a trigger in transitionList
cmp transitionCount, 0
je hint_none
mov edx, 0
mov eax, OFFSET transitionList
hint_check_loop:
cmp edx, transitionCount
jge hint_none
movzx ebx, [eax].LevelTransition.triggerCellX
cmp ebx, targetX
jne hint_check_next
movzx ebx, [eax].LevelTransition.triggerCellY
cmp ebx, targetY
je hint_portal
hint_check_next:
add eax, sizeof LevelTransition
inc edx
jmp hint_check_loop
hint_none:
xor eax, eax
ret
hint_door:
mov eax, 1
ret
hint_portal:
mov eax, 2
ret
CheckDoorHint ENDP
; ------------------------------------------------------------------------------
; CheckPortalTriggers: Checks if player stepped onto/into a transition trigger cell
; ------------------------------------------------------------------------------
CheckPortalTriggers PROC uses ebx ecx edx esi edi
cmp fadeState, 0
jne cpt_done
cmp transitionCount, 0
je cpt_done
; Current player cell X and Y
mov eax, playerX
sar eax, 6
mov ebx, eax
mov eax, playerY
sar eax, 6
mov ecx, eax
shl eax, 5
add eax, ebx
cmp eax, portalLatchCell
je cpt_done
mov portalLatchCell, eax
mov esi, OFFSET transitionList
mov edx, 0
cpt_loop:
cmp edx, transitionCount
jge cpt_done
movzx eax, [esi].LevelTransition.triggerCellX
cmp eax, ebx
jne cpt_next
movzx eax, [esi].LevelTransition.triggerCellY
cmp eax, ecx
jne cpt_next
; Walked into trigger cell! Initiate transition
movzx eax, [esi].LevelTransition.targetSpawnX
movzx ebx, [esi].LevelTransition.targetSpawnY
movzx ecx, [esi].LevelTransition.targetAngle
movzx edx, [esi].LevelTransition.fadeEffect
lea edi, [esi].LevelTransition.targetMap
invoke SwitchSublevel, edi, eax, ebx, ecx, edx
ret
cpt_next:
add esi, sizeof LevelTransition
inc edx
jmp cpt_loop
cpt_done:
ret
CheckPortalTriggers ENDP
; ------------------------------------------------------------------------------
; SwitchSublevel: Triggers transition sequence to target sublevel file
; ------------------------------------------------------------------------------
SwitchSublevel PROC uses esi edi, pszNextMap:DWORD, spawnX:DWORD, spawnY:DWORD, spawnAngle:DWORD, fadeEffect:DWORD
cmp fadeState, 0
jne switch_busy
; Even an instant room switch consumes this tick. AI/combat must not use
; pre-transition input in the newly loaded room.
mov gameplayTickBlocked, 1
mov esi, pszNextMap
lea edi, pendingMapPath
mov ecx, 32
copy_next_map:
lodsb
stosb
test al, al
jz next_map_copied
dec ecx
jnz copy_next_map
mov byte ptr [edi-1], 0
next_map_copied:
mov eax, spawnX
mov pendingSpawnX, eax
mov eax, spawnY
mov pendingSpawnY, eax
mov eax, spawnAngle
mov pendingSpawnAngle, eax
cmp fadeEffect, 0
je switch_instant
; Initiate smooth Fade Out (up to 16 fixed ticks, ~0.26s).
mov fadeState, 1
mov fadeLevel, 1
ret
switch_instant:
call DoSublevelTransition
switch_busy:
ret
SwitchSublevel ENDP
; ------------------------------------------------------------------------------
; DoSublevelTransition: Unloads old sector and loads new sector
; ------------------------------------------------------------------------------
DoSublevelTransition PROC uses ebx ecx edx
; The loader replaces the room only after validating every staged value.
invoke LoadSublevelBinary, OFFSET pendingMapPath, pendingSpawnX, pendingSpawnY, pendingSpawnAngle
test eax, eax
jnz transition_loaded
mov mapErrorFrames, 180
ret
transition_loaded:
call InitEntities
ret
DoSublevelTransition ENDP
; ------------------------------------------------------------------------------
; UpdateScreenFade: fixed-tick transition sequencer, never called from drawing.
; ------------------------------------------------------------------------------
UpdateScreenFade PROC
cmp fadeState, 0
je fade_tick_done
cmp fadeState, 1
je do_fade_out
cmp fadeState, 2
je do_switch_state
cmp fadeState, 3
je do_fade_in
jmp fade_tick_done
do_fade_out:
inc fadeLevel
cmp fadeLevel, 16
jl fade_tick_done
mov fadeLevel, 16
mov fadeState, 2
call DoSublevelTransition
mov fadeState, 3
jmp fade_tick_done
do_switch_state:
call DoSublevelTransition
mov fadeState, 3
jmp fade_tick_done
do_fade_in:
dec fadeLevel
cmp fadeLevel, 0
jg fade_tick_done
mov fadeLevel, 0
mov fadeState, 0
fade_tick_done:
ret
UpdateScreenFade ENDP
; Pure framebuffer post-process. Repeated drawing never switches rooms.
ApplyScreenFade PROC uses ebx ecx edx esi edi
apply_darkening:
cmp fadeLevel, 0
jle fade_done
cmp fadeLevel, 16
jl fade_math
; Fully black screen
mov edi, OFFSET screenBuffer
mov ecx, SCREEN_W * SCREEN_H
xor eax, eax
rep stosd
jmp fade_done
fade_math:
mov ebx, 16
sub ebx, fadeLevel ; scale: 1..15
mov esi, OFFSET screenBuffer
mov edi, esi
mov ecx, SCREEN_W * SCREEN_H
fade_pixel_loop:
mov eax, [esi]
; Blue
movzx edx, al
imul edx, ebx
shr edx, 4
; Green
movzx eax, ah
imul eax, ebx
shr eax, 4
shl eax, 8
or edx, eax
; Red
mov eax, [esi]
shr eax, 16
movzx eax, al
imul eax, ebx
shr eax, 4
shl eax, 16
or eax, edx
mov [edi], eax
add esi, 4
add edi, 4
dec ecx
jnz fade_pixel_loop
fade_done:
ret
ApplyScreenFade ENDP
; ------------------------------------------------------------------------------
; LoadSublevelBinary: Parameterized level loader for any .dat file
; Overrides player spawn if spawnX != -1, populates gameMap, sprites, transitions.
; Returns: eax = 1 on success, 0 on fallback
; ------------------------------------------------------------------------------
LoadSublevelBinary PROC uses ebx ecx edx esi edi, pszPath:DWORD, spawnX:DWORD, spawnY:DWORD, spawnAngle:DWORD
LOCAL hFile:DWORD, bytesRead:DWORD, header:MapHeader
LOCAL numSprites:DWORD, numTransitions:DWORD, index:DWORD
LOCAL numEvents:DWORD, numStrings:DWORD, sceneFlags:DWORD, numLights:DWORD
LOCAL resolvedPath[MAX_PATH_BYTES]:BYTE
LOCAL stagedMap[MAP_SIZE]:BYTE
LOCAL stagedSprites[MAX_SPRITES * (sizeof SpriteStruct)]:BYTE
LOCAL stagedTransitions[MAX_TRANSITIONS * (sizeof LevelTransition)]:BYTE
LOCAL newX:DWORD, newY:DWORD, newAngle:DWORD
; Relative map names live in assets. Accept an actual assets prefix,
; drive-qualified paths and absolute paths, never just the first letter.
mov esi, pszPath
test esi, esi
jz load_default_path
cmp byte ptr [esi], 0
jne resolve_map_path
load_default_path:
mov esi, OFFSET szMapFilePath
resolve_map_path:
lea edi, resolvedPath
mov ecx, MAX_PATH_BYTES - 1
cmp byte ptr [esi], 0
je load_failed
cmp byte ptr [esi+1], ':'
je copy_map_path
cmp byte ptr [esi], 92
je copy_map_path
cmp byte ptr [esi], '/'
je copy_map_path
push esi
push ecx
mov ebx, OFFSET szAssetsPrefix
mov ecx, 6
check_assets_prefix:
mov al, [esi]
or al, 20h
cmp al, [ebx]
jne prefix_not_assets
inc esi
inc ebx
loop check_assets_prefix
cmp byte ptr [esi], 92
je prefix_is_assets
cmp byte ptr [esi], '/'
je prefix_is_assets
prefix_not_assets:
pop ecx
pop esi
mov ebx, OFFSET szAssetsPrefix
copy_assets_prefix:
mov al, [ebx]
test al, al
jz copy_map_path
stosb
inc ebx
dec ecx
jmp copy_assets_prefix
prefix_is_assets:
pop ecx
pop esi
copy_map_path:
lodsb
stosb
test al, al
jz open_level
dec ecx
jnz copy_map_path
jmp load_failed
open_level:
invoke CreateFileA, ADDR resolvedPath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
mov hFile, eax
cmp eax, INVALID_HANDLE_VALUE
je load_failed
invoke ReadFile, hFile, ADDR header, sizeof MapHeader, ADDR bytesRead, 0
test eax, eax
jz close_load_failed
cmp bytesRead, sizeof MapHeader
jne close_load_failed
cmp header.magic, MAP_MAGIC
jne close_load_failed
cmp header.version, 1
jb close_load_failed
cmp header.version, MAP_FORMAT_VERSION
ja close_load_failed
cmp header.mapWidth, MAP_W
jne close_load_failed
cmp header.mapHeight, MAP_H
jne close_load_failed
cmp header.playerStartX, MAP_W
jae close_load_failed
cmp header.playerStartY, MAP_H
jae close_load_failed
cmp header.playerStartAngle, ANGLE_MASK
ja close_load_failed
movzx eax, header.spriteCount
cmp eax, MAX_SPRITES
ja close_load_failed
mov numSprites, eax
xor eax, eax
cmp header.version, 1
je old_map_no_transitions
; Accept the extended v2 files written by early EblanStudio builds.
movzx eax, header.transitionCount
old_map_no_transitions:
cmp eax, MAX_TRANSITIONS
ja close_load_failed
mov numTransitions, eax
mov numEvents, 0
mov numStrings, 0
mov sceneFlags, 0
cmp header.version, 4
jb old_map_no_cutscene
movzx eax, header.cutsceneCount
cmp eax, MAX_CUTSCENE_EVENTS
ja close_load_failed
mov numEvents, eax
movzx eax, header.cutsceneStringCount
cmp eax, MAX_CUTSCENE_STRINGS
ja close_load_failed
mov numStrings, eax
movzx eax, header.cutsceneFlags
mov sceneFlags, eax
old_map_no_cutscene:
mov numLights,0
cmp header.version,5
jb old_map_no_lighting
cmp header.lightingReserved,0
jne close_load_failed
movzx eax,header.lightCount
cmp eax,13
ja close_load_failed
mov numLights,eax
old_map_no_lighting:
mov eax, numTransitions
imul eax, sizeof LevelTransition
mov ebx, eax
mov eax, numSprites
imul eax, sizeof SpriteStruct
add ebx, eax
add ebx, sizeof MapHeader + MAP_SIZE
mov eax, numEvents
imul eax, sizeof CutsceneEvent
add ebx, eax
mov eax, numStrings
imul eax, CUTSCENE_STRING_BYTES
add ebx, eax
cmp header.version,5
jb map_lighting_size_ready
mov eax,numLights
imul eax,40
add ebx,eax
add ebx,32+4096
map_lighting_size_ready:
invoke GetFileSize, hFile, 0
cmp eax, ebx
jne close_load_failed
invoke ReadFile, hFile, ADDR stagedMap, MAP_SIZE, ADDR bytesRead, 0
test eax, eax
jz close_load_failed
cmp bytesRead, MAP_SIZE
jne close_load_failed
mov eax, numSprites
imul eax, sizeof SpriteStruct
mov ebx, eax
invoke ReadFile, hFile, ADDR stagedSprites, ebx, ADDR bytesRead, 0
test eax, eax
jz close_load_failed
cmp bytesRead, ebx
jne close_load_failed
mov eax, numTransitions
imul eax, sizeof LevelTransition
mov ebx, eax
invoke ReadFile, hFile, ADDR stagedTransitions, ebx, ADDR bytesRead, 0
test eax, eax
jz close_load_failed
cmp bytesRead, ebx
jne close_load_failed
mov ebx, numEvents
imul ebx, sizeof CutsceneEvent
invoke ReadFile, hFile, OFFSET stagedCutsceneEvents, ebx, ADDR bytesRead, 0
test eax, eax
jz close_load_failed
cmp bytesRead, ebx
jne close_load_failed
mov ebx, numStrings
imul ebx, CUTSCENE_STRING_BYTES
invoke ReadFile, hFile, OFFSET stagedCutsceneStrings, ebx, ADDR bytesRead, 0
test eax, eax
jz close_load_failed
cmp bytesRead, ebx
jne close_load_failed
mov esi,OFFSET legacyLightingSettings
mov edi,OFFSET stagedLightSettings
mov ecx,8
rep movsd
mov edi,OFFSET stagedLightRGB
mov ecx,MAP_SIZE
mov eax,00282828h
rep stosd
cmp header.version,5
jb staged_lighting_ready
invoke ReadFile,hFile,OFFSET stagedLightSettings,32,ADDR bytesRead,0
test eax,eax
jz close_load_failed
cmp bytesRead,32
jne close_load_failed
mov ebx,numLights
imul ebx,40
invoke ReadFile,hFile,OFFSET stagedLightSources,ebx,ADDR bytesRead,0
test eax,eax
jz close_load_failed
cmp bytesRead,ebx
jne close_load_failed
invoke ReadFile,hFile,OFFSET stagedLightRGB,4096,ADDR bytesRead,0
test eax,eax
jz close_load_failed
cmp bytesRead,4096
jne close_load_failed
invoke ValidateLightingData,OFFSET stagedLightSettings,OFFSET stagedLightSources,numLights
test eax,eax
jz close_load_failed
mov esi,OFFSET stagedLightRGB
mov ecx,MAP_SIZE
map_check_light_rgb:
lodsd
test eax,0FF000000h
jnz close_load_failed
loop map_check_light_rgb
staged_lighting_ready:
invoke ValidateStagedCutscene, numEvents, numStrings, sceneFlags, ADDR stagedMap, numTransitions
test eax, eax
jz close_load_failed
; Validate all payload data before touching the active room.
lea esi, stagedMap
mov ecx, MAP_SIZE
validate_grid:
lodsb
cmp al, 15
ja close_load_failed
loop validate_grid
lea esi, stagedSprites
mov ecx, numSprites
validate_sprite:
test ecx, ecx
jz validate_transitions
cmp header.version, 1
jne sprite_coords_raw
sar [esi].SpriteStruct.x, 16
sar [esi].SpriteStruct.y, 16
sprite_coords_raw:
cmp [esi].SpriteStruct.x, 0
jl close_load_failed
cmp [esi].SpriteStruct.x, MAP_W * CELL_SIZE
jge close_load_failed
cmp [esi].SpriteStruct.y, 0
jl close_load_failed
cmp [esi].SpriteStruct.y, MAP_H * CELL_SIZE
jge close_load_failed
cmp [esi].SpriteStruct.textureId, MAX_TEXTURES
jae close_load_failed
cmp [esi].SpriteStruct.scale, 0
jl close_load_failed
cmp [esi].SpriteStruct.scale, 16 * FIXED_ONE
ja close_load_failed
add esi, sizeof SpriteStruct
dec ecx
jmp validate_sprite
validate_transitions:
lea esi, stagedTransitions
mov index, 0
validate_transition:
mov eax, index
cmp eax, numTransitions
jae validate_portal_cells
cmp [esi].LevelTransition.triggerCellX, MAP_W
jae close_load_failed
cmp [esi].LevelTransition.triggerCellY, MAP_H
jae close_load_failed
cmp [esi].LevelTransition.targetSpawnX, MAP_W
jae close_load_failed
cmp [esi].LevelTransition.targetSpawnY, MAP_H
jae close_load_failed
cmp [esi].LevelTransition.targetAngle, ANGLE_MASK
ja close_load_failed
cmp [esi].LevelTransition.fadeEffect, 1
ja close_load_failed
push ecx
lea edi, [esi].LevelTransition.targetMap
invoke ValidateMapFilename, edi
pop ecx
test eax, eax
jz close_load_failed
movzx eax, [esi].LevelTransition.triggerCellY
shl eax, 5
movzx edx, [esi].LevelTransition.triggerCellX
add eax, edx
mov al, stagedMap[eax]
cmp al, CELL_EMPTY
je transition_grid_ok
cmp al, CELL_DOOR_OPEN
je transition_grid_ok
cmp al, CELL_PORTAL
jne close_load_failed
transition_grid_ok:
lea edi, stagedTransitions
mov ecx, index
unique_transition:
test ecx, ecx
jz transition_unique
mov ax, WORD PTR [esi]
cmp ax, WORD PTR [edi]
je close_load_failed
add edi, sizeof LevelTransition
dec ecx
jmp unique_transition
transition_unique:
add esi, sizeof LevelTransition
inc index
jmp validate_transition
validate_portal_cells:
xor ebx, ebx
portal_grid_loop:
cmp ebx, MAP_SIZE
jae validate_spawn
cmp stagedMap[ebx], CELL_PORTAL
jne next_portal_grid
lea esi, stagedTransitions
mov ecx, numTransitions
find_grid_transition:
test ecx, ecx
jz close_load_failed
movzx eax, [esi].LevelTransition.triggerCellY
shl eax, 5
movzx edx, [esi].LevelTransition.triggerCellX
add eax, edx
cmp eax, ebx
je next_portal_grid
add esi, sizeof LevelTransition
dec ecx
jmp find_grid_transition
next_portal_grid:
inc ebx
jmp portal_grid_loop
validate_spawn:
movzx eax, header.playerStartX
movzx ebx, header.playerStartY
movzx ecx, header.playerStartAngle
cmp spawnX, -1
je spawn_values_ready
mov eax, spawnX
mov ebx, spawnY
mov ecx, spawnAngle
spawn_values_ready:
cmp eax, MAP_W
jae close_load_failed
cmp ebx, MAP_H
jae close_load_failed
cmp ecx, ANGLE_MASK
ja close_load_failed
mov newX, eax
mov newY, ebx
mov newAngle, ecx
shl ebx, 5
add ebx, eax
mov al, stagedMap[ebx]
cmp al, CELL_EMPTY
je commit_level
cmp al, CELL_DOOR_OPEN
je commit_level
cmp al, CELL_PORTAL
jne close_load_failed
commit_level:
invoke CloseHandle, hFile
call StopCutscene
mov esi, OFFSET stagedCutsceneEvents
mov edi, OFFSET cutsceneEvents
mov ecx, numEvents
shl ecx, 2
rep movsd
mov esi, OFFSET stagedCutsceneStrings
mov edi, OFFSET cutsceneStrings
mov ecx, numStrings
imul ecx, CUTSCENE_STRING_BYTES / 4
rep movsd
mov eax, numEvents
mov cutsceneCount, eax
mov eax, numStrings
mov cutsceneStringCount, eax
mov eax, sceneFlags
mov cutsceneFlags, eax
and eax, CUTSCENE_AUTOPLAY
mov cutscenePending, eax
lea esi, stagedMap
mov edi, OFFSET gameMap
mov ecx, MAP_SIZE / 4
rep movsd
lea esi, stagedSprites
mov edi, OFFSET spriteList
mov ecx, numSprites
imul ecx, sizeof SpriteStruct / 4
rep movsd
lea esi, stagedTransitions
mov edi, OFFSET transitionList
mov ecx, numTransitions
imul ecx, sizeof LevelTransition
rep movsb
mov eax, numSprites
mov spriteCount, eax
mov eax, numTransitions
mov transitionCount, eax
mov eax, header.ceilingColor
and eax, 00FFFFFFh
mov mapCeilingColor, eax
mov eax, header.floorColor
and eax, 00FFFFFFh
mov mapFloorColor, eax
mov eax, newY
shl eax, 5
add eax, newX
mov portalLatchCell, eax
mov eax, newX
shl eax, CELL_SHIFT
add eax, CELL_SIZE / 2
mov playerX, eax
mov eax, newY
shl eax, CELL_SHIFT
add eax, CELL_SIZE / 2
mov playerY, eax
mov eax, newAngle
mov playerAngle, eax
mov eax, newX
mov roomEntryX, eax
mov eax, newY
mov roomEntryY, eax
mov eax, newAngle
mov roomEntryAngle, eax
lea esi, resolvedPath
mov edi, OFFSET currentMapPath
remember_room_path:
lodsb
stosb
test al, al
jnz remember_room_path
call ResetMovementFractions
mov mapErrorFrames, 0
invoke CommitLightingData,OFFSET stagedLightSettings,OFFSET stagedLightSources,numLights,OFFSET stagedLightRGB
mov eax, 1
ret
close_load_failed:
invoke CloseHandle, hFile
load_failed:
xor eax, eax
ret
LoadSublevelBinary ENDP
; Map references in EBLN are bounded ASCII basenames, never arbitrary pointers.
ValidateMapFilename PROC uses ebx ecx edx esi, pszName:DWORD
mov esi, pszName
xor ecx, ecx
filename_character:
mov al, [esi+ecx]
test al, al
jz filename_end
cmp al, 32
jb filename_invalid
cmp al, 127
jae filename_invalid
cmp al, 92
je filename_invalid
cmp al, '/'
je filename_invalid
cmp al, ':'
je filename_invalid
cmp al, '*'
je filename_invalid
cmp al, '?'
je filename_invalid
cmp al, 34
je filename_invalid
cmp al, '<'
je filename_invalid
cmp al, '>'
je filename_invalid
cmp al, '|'
je filename_invalid
inc ecx
cmp ecx, 32
jb filename_character
jmp filename_invalid
filename_end:
cmp ecx, 5
jb filename_invalid
mov eax, DWORD PTR [esi+ecx-4]
or eax, 20202000h
cmp eax, 7461642Eh ; .dat
jne filename_invalid
mov eax, 1
ret
filename_invalid:
xor eax, eax
ret
ValidateMapFilename ENDP
LoadMapBinary PROC
mov eax, OFFSET szMapFilePath
cmp startupMapPath, 0
je load_startup_map
mov eax, OFFSET startupMapPath
load_startup_map:
invoke LoadSublevelBinary, eax, -1, -1, 0
test eax, eax
jnz startup_map_ready
call InitMap
startup_map_ready:
push eax
call InitEntities
pop eax
ret
LoadMapBinary ENDP
; Reload the current room and the actual entry spawn, not the startup map.
; LoadSublevelBinary commits only on success, so failure preserves all gameplay.
RestartCurrentRoom PROC
cmp currentMapPath, 0
je restart_procedural
invoke LoadSublevelBinary, OFFSET currentMapPath, roomEntryX, roomEntryY, roomEntryAngle
test eax, eax
jz restart_failed
jmp restart_committed
restart_procedural:
call InitMap
restart_committed:
call InitEntities
call ResetMovementFractions
mov playerHealth, 100
mov playerAmmo, 50
mov playerScore, 0
mov playerKeys, 0
mov isPlayerDead, 0
mov isGamePaused, 0
mov gameDialogActive, 0
mov gameToastTimer, 0
mov playerAttackCooldown, 0
mov playerFlashTimer, 0
mov playerHitFlash, 0
mov fadeState, 0
mov fadeLevel, 0
mov mapErrorFrames, 0
mov pendingMapPath, 0
mov pendingSpawnX, 0
mov pendingSpawnY, 0
mov pendingSpawnAngle, 0
mov gameplayTickBlocked, 1
mov clockReset, 1
mov eax, 1
ret
restart_failed:
xor eax, eax
ret
RestartCurrentRoom ENDP