Preserve stage-1 stability fixes alongside v5 lighting, IPC v2, weapons and studio updates. Fix barrel AOE pointer corruption and extend modal gameplay gating. Verify 137 editor checks, 191 ASM checks, lighting bake and preview parity. Document stage-2 scope without implementing it.
1042 lines
27 KiB
PHP
1042 lines
27 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
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; InitMap: Creates a rich test level with rooms, hallways, pillars & doors
|
|
; ------------------------------------------------------------------------------
|
|
InitMap PROC uses eax ecx edi
|
|
; 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
|
|
call StopCutscene
|
|
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
|
|
|
|
; Close the 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 (takes 16 frames ~ 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
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; ApplyScreenFade: Screen darkening post-processing and transition sequencer
|
|
; ------------------------------------------------------------------------------
|
|
ApplyScreenFade PROC uses ebx ecx edx esi edi
|
|
cmp fadeState, 0
|
|
je fade_done
|
|
|
|
cmp fadeState, 1
|
|
je do_fade_out
|
|
cmp fadeState, 2
|
|
je do_switch_state
|
|
cmp fadeState, 3
|
|
je do_fade_in
|
|
jmp fade_done
|
|
|
|
do_fade_out:
|
|
inc fadeLevel
|
|
cmp fadeLevel, 16
|
|
jl apply_darkening
|
|
mov fadeLevel, 16
|
|
mov fadeState, 2
|
|
call DoSublevelTransition
|
|
mov fadeState, 3
|
|
jmp apply_darkening
|
|
|
|
do_switch_state:
|
|
call DoSublevelTransition
|
|
mov fadeState, 3
|
|
jmp apply_darkening
|
|
|
|
do_fade_in:
|
|
dec fadeLevel
|
|
cmp fadeLevel, 0
|
|
jg apply_darkening
|
|
mov fadeLevel, 0
|
|
mov fadeState, 0
|
|
jmp fade_done
|
|
|
|
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 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
|