Group level edits across inspector and canvas gestures, preserve legacy maps, and expose opt-in switch/key door bindings without changing wire layouts. Refresh studio and pause UI with UE5-inspired surfaces and content drawer. Verify 162 editor and 307 ASM checks plus renderer parity.
1234 lines
32 KiB
PHP
1234 lines
32 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
|
|
; ------------------------------------------------------------------------------
|
|
; Returns resulting cell or zero. Shared by direct interaction and switches.
|
|
ToggleDoorCell PROC uses ebx ecx edx, cellX:DWORD, cellY:DWORD
|
|
invoke GetMapCell, cellX, cellY
|
|
cmp eax, CELL_DOOR
|
|
jne toggle_check_close
|
|
invoke SetMapCell, cellX, cellY, CELL_DOOR_OPEN
|
|
mov eax, CELL_DOOR_OPEN
|
|
ret
|
|
toggle_check_close:
|
|
cmp eax, CELL_DOOR_OPEN
|
|
jne toggle_blocked
|
|
mov eax, playerX
|
|
sub eax, playerRadius
|
|
sar eax, CELL_SHIFT
|
|
cmp eax, cellX
|
|
jg toggle_close
|
|
mov eax, playerX
|
|
add eax, playerRadius
|
|
sar eax, CELL_SHIFT
|
|
cmp eax, cellX
|
|
jl toggle_close
|
|
mov eax, playerY
|
|
sub eax, playerRadius
|
|
sar eax, CELL_SHIFT
|
|
cmp eax, cellY
|
|
jg toggle_close
|
|
mov eax, playerY
|
|
add eax, playerRadius
|
|
sar eax, CELL_SHIFT
|
|
cmp eax, cellY
|
|
jge toggle_blocked
|
|
toggle_close:
|
|
invoke SetMapCell, cellX, cellY, CELL_DOOR
|
|
mov eax, CELL_DOOR
|
|
ret
|
|
toggle_blocked:
|
|
xor eax, eax
|
|
ret
|
|
ToggleDoorCell ENDP
|
|
|
|
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
|
|
je interact_door_found
|
|
cmp eax, CELL_DOOR_OPEN
|
|
jne check_portal_interact
|
|
interact_door_found:
|
|
mov keyAction, 0
|
|
; A bound door is switch-only, or key-unlockable. Ordinary doors stay free.
|
|
mov edi, targetY
|
|
shl edi, CELL_SHIFT-1 ; MAP_W is 32
|
|
add edi, targetX
|
|
cmp byte ptr [doorUnlocked+edi], 0
|
|
jne interact_toggle
|
|
mov esi, OFFSET spriteList
|
|
mov ecx, spriteCount
|
|
interact_binding_loop:
|
|
test ecx, ecx
|
|
jz interact_toggle
|
|
mov eax, [esi].SpriteStruct.flags
|
|
cmp al, ENTITY_TYPE_SWITCH
|
|
jne interact_binding_next
|
|
mov edx, eax
|
|
and edx, 0FEFFh
|
|
cmp edx, 0A805h
|
|
jne interact_binding_next
|
|
mov edx, eax
|
|
shr edx, 16
|
|
and edx, 0FFh
|
|
cmp edx, targetX
|
|
jne interact_binding_next
|
|
mov edx, eax
|
|
shr edx, 24
|
|
cmp edx, targetY
|
|
jne interact_binding_next
|
|
test eax, 100h
|
|
jz interact_locked
|
|
cmp playerKeys, 0
|
|
je interact_locked
|
|
dec playerKeys
|
|
mov byte ptr [doorUnlocked+edi], 1
|
|
jmp interact_toggle
|
|
interact_binding_next:
|
|
add esi, sizeof SpriteStruct
|
|
dec ecx
|
|
jmp interact_binding_loop
|
|
interact_locked:
|
|
invoke ShowGameToast, OFFSET szDoorLockedToast, 50, COLOR_YELLOW
|
|
ret
|
|
interact_toggle:
|
|
invoke ToggleDoorCell, targetX, targetY
|
|
cmp eax, CELL_DOOR_OPEN
|
|
je interact_opened
|
|
cmp eax, CELL_DOOR
|
|
jne no_door
|
|
invoke ShowGameToast, OFFSET szDoorClosedToast, 50, COLOR_YELLOW
|
|
ret
|
|
interact_opened:
|
|
invoke ShowGameToast, OFFSET szDoorOpenedToast, 50, COLOR_GREEN
|
|
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!
|
|
mov keyAction, 0
|
|
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
|
|
mov eax, [esi].SpriteStruct.flags
|
|
cmp al, ENTITY_TYPE_SWITCH
|
|
jne validated_sprite_binding
|
|
mov edx, eax
|
|
and edx, 0FEFFh
|
|
cmp edx, 0A805h
|
|
jne validated_sprite_binding
|
|
mov edx, eax
|
|
shr edx, 24
|
|
cmp edx, MAP_H
|
|
jae close_load_failed
|
|
shr eax, 16
|
|
and eax, 0FFh
|
|
cmp eax, MAP_W
|
|
jae close_load_failed
|
|
shl edx, 5
|
|
add edx, eax
|
|
mov al, stagedMap[edx]
|
|
cmp al, CELL_DOOR
|
|
je binding_cell_ok
|
|
cmp al, CELL_DOOR_OPEN
|
|
jne close_load_failed
|
|
binding_cell_ok:
|
|
lea edi, stagedSprites
|
|
binding_unique_loop:
|
|
cmp edi, esi
|
|
jae validated_sprite_binding
|
|
mov eax, [edi].SpriteStruct.flags
|
|
cmp al, ENTITY_TYPE_SWITCH
|
|
jne binding_unique_next
|
|
mov edx, eax
|
|
and edx, 0FEFFh
|
|
cmp edx, 0A805h
|
|
jne binding_unique_next
|
|
shr eax, 16
|
|
mov edx, [esi].SpriteStruct.flags
|
|
shr edx, 16
|
|
cmp eax, edx
|
|
je close_load_failed
|
|
binding_unique_next:
|
|
add edi, sizeof SpriteStruct
|
|
jmp binding_unique_loop
|
|
validated_sprite_binding:
|
|
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
|