EBLAN_ENGINE/include/engine_combat.inc
Игорь Федосеев f42679fe62 fix: stabilize gameplay and IDE source writes
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.
2026-10-31 21:56:54 +03:00

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