Trace shots against current map geometry instead of stale render depth. Preserve outline counters across volatile pixel calls and freeze gameplay on cinematic, transition and death ticks. Clamp packed HP before narrowing and match legacy archetype rules. Publish IDE saves atomically; failed saves block file switches and builds. Drain compiler output while hidden. Verify runtime/editor builds, 112 editor checks, 148 ASM checks and live preview parity. Record remaining stages in docs/STABILIZATION.md.
433 lines
10 KiB
PHP
433 lines
10 KiB
PHP
; ==============================================================================
|
|
; EBLAN ENGINE - Player Combat, Weapon Shooting & Combat HUD
|
|
; ==============================================================================
|
|
|
|
.code
|
|
|
|
DrawPixel PROTO :DWORD, :DWORD, :DWORD
|
|
DrawRect PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
DrawRectOutline PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
|
|
DrawString8x8 PROTO :DWORD, :DWORD, :DWORD, :DWORD
|
|
DrawNumber8x8 PROTO :DWORD, :DWORD, :DWORD, :DWORD
|
|
|
|
; Trace the current world along the player's forward direction. EAX = first
|
|
; blocking boundary in 24.8 world units. Glass blocks shots (unlike rendering).
|
|
; No framebuffer/depth-buffer dependencies. Out-of-map cells are solid.
|
|
TraceShotWall PROC uses ebx ecx edx esi edi
|
|
LOCAL cellX:DWORD, cellY:DWORD, stepX:DWORD, stepY:DWORD
|
|
LOCAL deltaX:DWORD, deltaY:DWORD, sideX:DWORD, sideY:DWORD
|
|
LOCAL hitDistance:DWORD, remaining:DWORD
|
|
|
|
mov eax, playerX
|
|
sar eax, 6
|
|
mov cellX, eax
|
|
mov eax, playerY
|
|
sar eax, 6
|
|
mov cellY, eax
|
|
invoke GetMapCell, cellX, cellY
|
|
invoke IsSolidCell, eax
|
|
test eax, eax
|
|
jnz shot_wall_at_origin
|
|
|
|
mov eax, playerAngle
|
|
and eax, ANGLE_MASK
|
|
mov esi, [tableCos+eax*4]
|
|
mov edi, [tableSin+eax*4]
|
|
mov deltaX, 3FFFFFFFh
|
|
mov sideX, 3FFFFFFFh
|
|
mov stepX, 1
|
|
test esi, esi
|
|
jz shot_setup_y
|
|
mov eax, esi
|
|
cdq
|
|
xor eax, edx
|
|
sub eax, edx
|
|
mov ecx, eax
|
|
mov eax, (64 shl 24)
|
|
xor edx, edx
|
|
div ecx
|
|
mov deltaX, eax
|
|
mov eax, playerX
|
|
and eax, 63
|
|
test esi, esi
|
|
js shot_negative_x
|
|
neg eax
|
|
add eax, 64
|
|
jmp shot_side_x
|
|
shot_negative_x:
|
|
mov stepX, -1
|
|
shot_side_x:
|
|
imul deltaX
|
|
shrd eax, edx, 6
|
|
mov sideX, eax
|
|
shot_setup_y:
|
|
mov deltaY, 3FFFFFFFh
|
|
mov sideY, 3FFFFFFFh
|
|
mov stepY, 1
|
|
test edi, edi
|
|
jz shot_traverse_start
|
|
mov eax, edi
|
|
cdq
|
|
xor eax, edx
|
|
sub eax, edx
|
|
mov ecx, eax
|
|
mov eax, (64 shl 24)
|
|
xor edx, edx
|
|
div ecx
|
|
mov deltaY, eax
|
|
mov eax, playerY
|
|
and eax, 63
|
|
test edi, edi
|
|
js shot_negative_y
|
|
neg eax
|
|
add eax, 64
|
|
jmp shot_side_y
|
|
shot_negative_y:
|
|
mov stepY, -1
|
|
shot_side_y:
|
|
imul deltaY
|
|
shrd eax, edx, 6
|
|
mov sideY, eax
|
|
shot_traverse_start:
|
|
mov remaining, MAP_W+MAP_H+2
|
|
shot_traverse:
|
|
mov eax, sideX
|
|
cmp eax, sideY
|
|
jb shot_step_x
|
|
jne shot_step_y
|
|
; At a corner also test the X-adjacent cell: no shots through wall seams.
|
|
mov ebx, cellX
|
|
add ebx, stepX
|
|
invoke GetMapCell, ebx, cellY
|
|
invoke IsSolidCell, eax
|
|
test eax, eax
|
|
jz shot_step_y
|
|
mov eax, sideX
|
|
ret
|
|
shot_step_x:
|
|
mov hitDistance, eax
|
|
add eax, deltaX
|
|
mov sideX, eax
|
|
mov eax, stepX
|
|
add cellX, eax
|
|
jmp shot_test_cell
|
|
shot_step_y:
|
|
mov eax, sideY
|
|
mov hitDistance, eax
|
|
add eax, deltaY
|
|
mov sideY, eax
|
|
mov eax, stepY
|
|
add cellY, eax
|
|
shot_test_cell:
|
|
invoke GetMapCell, cellX, cellY
|
|
invoke IsSolidCell, eax
|
|
test eax, eax
|
|
jnz shot_wall_found
|
|
dec remaining
|
|
jnz shot_traverse
|
|
; Defensive bound: a valid ray exits a 32x32 map within 64 crossings.
|
|
shot_wall_found:
|
|
mov eax, hitDistance
|
|
ret
|
|
shot_wall_at_origin:
|
|
xor eax, eax
|
|
ret
|
|
TraceShotWall ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; UpdateCombat: Process weapon firing, hitscan raycasting, cooldowns & damage
|
|
; ------------------------------------------------------------------------------
|
|
UpdateCombat PROC uses ebx ecx edx esi edi
|
|
LOCAL camCos:DWORD, camSin:DWORD
|
|
LOCAL i:DWORD, closestIdx:DWORD, closestDist:DWORD
|
|
LOCAL sprX:DWORD, sprY:DWORD
|
|
LOCAL transX:DWORD, transY:DWORD
|
|
LOCAL targetHalfWidth:DWORD, wallDepth:DWORD
|
|
|
|
call IsGameplayBlocked
|
|
test eax, eax
|
|
jnz combat_done
|
|
|
|
; 1. Decrement cooldowns
|
|
cmp playerAttackCooldown, 0
|
|
je @F
|
|
dec playerAttackCooldown
|
|
@@:
|
|
cmp playerFlashTimer, 0
|
|
je @F
|
|
dec playerFlashTimer
|
|
@@:
|
|
cmp playerHitFlash, 0
|
|
je @F
|
|
dec playerHitFlash
|
|
@@:
|
|
|
|
; If player is dead, cannot attack
|
|
cmp isPlayerDead, 0
|
|
jne combat_done
|
|
|
|
; 2. Check attack input
|
|
cmp keyAttack, 0
|
|
je combat_done
|
|
cmp playerAttackCooldown, 0
|
|
jne combat_done
|
|
|
|
; Check ammo
|
|
cmp playerAmmo, 0
|
|
jle combat_dry_fire
|
|
|
|
; Fire weapon!
|
|
dec playerAmmo
|
|
mov playerAttackCooldown, 10
|
|
mov playerFlashTimer, 5
|
|
|
|
; 3. Hitscan check against enemies in center crosshair
|
|
; Camera basis for -playerAngle
|
|
mov eax, ANGLE_512
|
|
sub eax, playerAngle
|
|
and eax, ANGLE_MASK
|
|
mov ecx, [tableCos + eax*4]
|
|
mov camCos, ecx
|
|
mov edx, [tableSin + eax*4]
|
|
mov camSin, edx
|
|
|
|
; Query current geometry, never the previous rendered frame.
|
|
call TraceShotWall
|
|
mov wallDepth, eax
|
|
|
|
mov closestIdx, -1
|
|
mov closestDist, 999999
|
|
|
|
mov i, 0
|
|
hitscan_loop:
|
|
mov eax, i
|
|
cmp eax, spriteCount
|
|
jge hitscan_eval
|
|
|
|
mov ecx, i
|
|
mov eax, [entityType + ecx*4]
|
|
cmp eax, ENTITY_TYPE_GUARD
|
|
jne next_hitscan_target
|
|
|
|
mov eax, [entityState + ecx*4]
|
|
cmp eax, ENTITY_STATE_DEAD
|
|
je next_hitscan_target
|
|
|
|
mov ebx, i
|
|
imul ebx, sizeof SpriteStruct
|
|
add ebx, OFFSET spriteList
|
|
|
|
mov eax, [ebx].SpriteStruct.textureId
|
|
test eax, eax
|
|
jz next_hitscan_target
|
|
|
|
; Transform to camera space
|
|
mov eax, [ebx].SpriteStruct.x
|
|
sub eax, playerX
|
|
mov sprX, eax
|
|
|
|
mov eax, [ebx].SpriteStruct.y
|
|
sub eax, playerY
|
|
mov sprY, eax
|
|
|
|
; transY = sprX * camCos - sprY * camSin
|
|
mov eax, sprX
|
|
imul camCos
|
|
shrd eax, edx, 16
|
|
mov ecx, eax
|
|
mov eax, sprY
|
|
imul camSin
|
|
shrd eax, edx, 16
|
|
sub ecx, eax
|
|
mov transY, ecx
|
|
|
|
; Must be in front of player
|
|
cmp transY, 16
|
|
jle next_hitscan_target
|
|
|
|
; Compare in 24.8 so a target on a wall boundary is not hit through it.
|
|
mov eax, transY
|
|
shl eax, 8
|
|
cmp eax, wallDepth
|
|
jge next_hitscan_target
|
|
|
|
; transX = sprX * camSin + sprY * camCos
|
|
mov eax, sprX
|
|
imul camSin
|
|
shrd eax, edx, 16
|
|
mov ecx, eax
|
|
mov eax, sprY
|
|
imul camCos
|
|
shrd eax, edx, 16
|
|
add ecx, eax
|
|
mov transX, ecx
|
|
|
|
; World-space billboard hitbox: 32 units wide at scale 1.0, scaled with
|
|
; the authored sprite, never with screen distance. Zero means legacy 1.0.
|
|
mov eax, [ebx].SpriteStruct.scale
|
|
test eax, eax
|
|
jnz @F
|
|
mov eax, FIXED_ONE
|
|
@@:
|
|
imul eax, 16
|
|
sar eax, 16
|
|
mov targetHalfWidth, eax
|
|
mov eax, transX
|
|
cdq
|
|
xor eax, edx
|
|
sub eax, edx
|
|
cmp eax, targetHalfWidth
|
|
jg next_hitscan_target
|
|
|
|
; Check if closer than previous target
|
|
mov eax, transY
|
|
cmp eax, closestDist
|
|
jge next_hitscan_target
|
|
mov closestDist, eax
|
|
mov eax, i
|
|
mov closestIdx, eax
|
|
|
|
next_hitscan_target:
|
|
inc i
|
|
jmp hitscan_loop
|
|
|
|
hitscan_eval:
|
|
cmp closestIdx, -1
|
|
je combat_done
|
|
|
|
; Apply damage to hit guard
|
|
mov ecx, closestIdx
|
|
mov eax, [entityHealth + ecx*4]
|
|
sub eax, 35
|
|
jns @F
|
|
xor eax, eax
|
|
@@:
|
|
mov [entityHealth + ecx*4], eax
|
|
test eax, eax
|
|
jnz combat_done
|
|
|
|
; Guard died!
|
|
mov [entityState + ecx*4], ENTITY_STATE_DEAD
|
|
mov DWORD PTR [entityTimer + ecx*4], 0
|
|
add playerScore, 100
|
|
|
|
; Drop 10 ammo reward
|
|
mov eax, playerAmmo
|
|
add eax, 10
|
|
cmp eax, playerMaxAmmo
|
|
jle @F
|
|
mov eax, playerMaxAmmo
|
|
@@:
|
|
mov playerAmmo, eax
|
|
jmp combat_done
|
|
|
|
combat_dry_fire:
|
|
mov playerAttackCooldown, 12
|
|
|
|
combat_done:
|
|
ret
|
|
UpdateCombat ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; RenderCombatHUD: Renders health bar, ammo, crosshair, weapon & death screen
|
|
; ------------------------------------------------------------------------------
|
|
RenderCombatHUD PROC uses ebx ecx edx esi edi
|
|
LOCAL barW:DWORD, gunY:DWORD
|
|
|
|
; 1. Center Crosshair (subtle cross at 160, 120)
|
|
invoke DrawRect, 156, 120, 9, 1, 00FFFFFFh
|
|
invoke DrawRect, 160, 116, 1, 9, 00FFFFFFh
|
|
invoke DrawRect, 160, 120, 1, 1, 0000FF00h
|
|
|
|
; 2. Render Retro Weapon at bottom center
|
|
mov gunY, 192
|
|
cmp playerFlashTimer, 0
|
|
je draw_gun_barrel
|
|
; Recoil offset
|
|
add gunY, 3
|
|
|
|
; Muzzle Flash Burst (Bright yellow & white star)
|
|
invoke DrawRect, 157, 165, 7, 18, 00FFFF00h
|
|
invoke DrawRect, 152, 170, 17, 8, 00FFAA00h
|
|
invoke DrawRect, 158, 168, 5, 12, 00FFFFFFh
|
|
|
|
draw_gun_barrel:
|
|
; Weapon barrel & slide
|
|
invoke DrawRect, 154, gunY, 13, 28, 003A3A3Ah
|
|
invoke DrawRect, 156, gunY, 9, 28, 00555555h
|
|
invoke DrawRect, 158, gunY, 5, 28, 00777777h ; highlight strip
|
|
; Gun frame & grip
|
|
mov eax, gunY
|
|
add eax, 24
|
|
invoke DrawRect, 150, eax, 21, 24, 00252525h
|
|
mov eax, gunY
|
|
add eax, 26
|
|
invoke DrawRect, 152, eax, 17, 22, 00453020h ; brown wood grip plates
|
|
|
|
; 3. Health Bar
|
|
invoke DrawString8x8, 8, 42, OFFSET strHpLabel, COLOR_GREEN
|
|
invoke DrawNumber8x8, 34, 42, playerHealth, COLOR_WHITE
|
|
|
|
invoke DrawRect, 68, 42, 60, 8, 00441010h
|
|
invoke DrawRectOutline, 68, 42, 60, 8, COLOR_WHITE
|
|
|
|
mov eax, playerHealth
|
|
imul eax, 58
|
|
xor edx, edx
|
|
mov ecx, playerMaxHealth
|
|
test ecx, ecx
|
|
jnz @F
|
|
mov ecx, 100
|
|
@@:
|
|
div ecx
|
|
mov barW, eax
|
|
cmp barW, 0
|
|
jle hud_ammo_draw
|
|
cmp barW, 58
|
|
jle @F
|
|
mov barW, 58
|
|
@@:
|
|
mov edx, 0000CC33h ; Green
|
|
cmp playerHealth, 30
|
|
jg @F
|
|
mov edx, 00FF2222h ; Red warning
|
|
@@:
|
|
invoke DrawRect, 69, 43, barW, 6, edx
|
|
|
|
hud_ammo_draw:
|
|
; 4. Ammo Display
|
|
invoke DrawString8x8, 140, 42, OFFSET strAmmoLabel, COLOR_YELLOW
|
|
invoke DrawNumber8x8, 182, 42, playerAmmo, COLOR_YELLOW
|
|
|
|
; 5. Score Display
|
|
invoke DrawString8x8, 222, 42, OFFSET strScoreLabel, COLOR_CYAN
|
|
invoke DrawNumber8x8, 272, 42, playerScore, COLOR_CYAN
|
|
|
|
; 6. Keys (if collected)
|
|
cmp playerKeys, 0
|
|
jle hud_damage_flash
|
|
invoke DrawString8x8, 222, 10, OFFSET strKeysLabel, COLOR_MAGENTA
|
|
invoke DrawNumber8x8, 258, 10, playerKeys, COLOR_MAGENTA
|
|
|
|
hud_damage_flash:
|
|
; 7. Damage Screen Flash
|
|
cmp playerHitFlash, 0
|
|
jle hud_death_check
|
|
; Red border vignette
|
|
invoke DrawRect, 0, 0, SCREEN_W, 3, 00FF0000h
|
|
invoke DrawRect, 0, 237, SCREEN_W, 3, 00FF0000h
|
|
invoke DrawRect, 0, 0, 3, SCREEN_H, 00FF0000h
|
|
invoke DrawRect, 317, 0, 3, SCREEN_H, 00FF0000h
|
|
|
|
hud_death_check:
|
|
; 8. Death Overlay Screen
|
|
cmp isPlayerDead, 0
|
|
je combat_hud_done
|
|
|
|
invoke DrawRect, 20, 95, 280, 50, 00880000h
|
|
invoke DrawRectOutline, 20, 95, 280, 50, COLOR_WHITE
|
|
invoke DrawString8x8, 32, 114, OFFSET strYouDied, COLOR_YELLOW
|
|
|
|
combat_hud_done:
|
|
ret
|
|
RenderCombatHUD ENDP
|