EBLAN_ENGINE/include/engine_combat.inc
Игорь Федосеев 527d92411d chore: integrate main before stage 2
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.
2026-10-31 22:10:07 +03:00

734 lines
19 KiB
PHP

; ==============================================================================
; EBLAN ENGINE - Player Combat, Multi-Weapon Arsenal & 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
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
Game_OnEntityKilled PROTO :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
LOCAL weaponDamage:DWORD, maxHitDist:DWORD, hitHalfWidth: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, game is paused, or in peaceful adventure mode, cannot attack
cmp isPlayerDead, 0
jne combat_done
cmp isGamePaused, 0
jne combat_done
cmp gameMode, GAME_MODE_ADVENTURE
je combat_done
cmp enableWeapons, 0
je combat_done
cmp playerWeapon, WEAPON_NONE
je combat_done
; 2. Check attack input
cmp keyAttack, 0
je combat_done
cmp playerAttackCooldown, 0
jne combat_done
; Set weapon parameters based on playerWeapon
mov weaponDamage, 35
mov maxHitDist, 999999
mov hitHalfWidth, 16
cmp playerWeapon, WEAPON_KNIFE
je setup_knife
cmp playerWeapon, WEAPON_SHOTGUN
je setup_shotgun
jmp setup_pistol
setup_knife:
; Knife: no ammo needed, close range (within 64 units = 1 cell), fast cooldown
mov weaponDamage, 30
mov maxHitDist, 64
mov hitHalfWidth, 24
mov playerAttackCooldown, 8
mov playerFlashTimer, 3
jmp fire_hitscan
setup_shotgun:
; Shotgun: needs 2 ammo, high damage (70), wider world hitbox, long cooldown
cmp playerAmmo, 2
jl combat_dry_fire
sub playerAmmo, 2
mov weaponDamage, 70
mov maxHitDist, 999999
mov hitHalfWidth, 32
mov playerAttackCooldown, 22
mov playerFlashTimer, 7
invoke SpawnMuzzleFlashLight, playerX, playerY
jmp fire_hitscan
setup_pistol:
; Pistol: needs 1 ammo, accurate world hitbox, medium cooldown (12)
cmp playerAmmo, 1
jl combat_dry_fire
dec playerAmmo
mov weaponDamage, 35
mov maxHitDist, 999999
mov hitHalfWidth, 16
mov playerAttackCooldown, 12
mov playerFlashTimer, 5
invoke SpawnMuzzleFlashLight, playerX, playerY
fire_hitscan:
; 3. Hitscan check against enemies & destructible barrels in 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]
; Check if Guard or Barrel
cmp eax, ENTITY_TYPE_GUARD
je hitscan_target_valid
cmp eax, ENTITY_TYPE_BARREL
je hitscan_target_valid
jmp next_hitscan_target
hitscan_target_valid:
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
; Must be closer than maximum weapon hit distance
mov eax, transY
cmp eax, maxHitDist
jg 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: weapon-specific width, 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, hitHalfWidth
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
mov ecx, closestIdx
mov eax, [entityType + ecx*4]
; Check if hit an explosive barrel!
cmp eax, ENTITY_TYPE_BARREL
je explode_barrel
; Hit an enemy guard!
mov eax, [entityHealth + ecx*4]
sub eax, weaponDamage
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
invoke Game_OnEntityKilled, closestIdx, ENTITY_TYPE_GUARD
; Drop ammo reward
mov eax, playerAmmo
add eax, 10
cmp eax, playerMaxAmmo
jle @F
mov eax, playerMaxAmmo
@@:
mov playerAmmo, eax
jmp combat_done
explode_barrel:
; Exploding Barrel: Destroy barrel and trigger AOE blast!
mov [entityState + ecx*4], ENTITY_STATE_DEAD
mov ebx, closestIdx
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList
mov [ebx].SpriteStruct.textureId, 0 ; disappear
; Fiery orange-red screen flash
mov playerHitFlash, 8
add playerScore, 200
invoke ShowGameToast, OFFSET szBarrelExplodedToast, 60, 00FFA020h
invoke Game_OnEntityKilled, closestIdx, ENTITY_TYPE_BARREL
; AOE damage to all surrounding guards within 128 units (2 cells)
push esi
push edi
mov esi, [ebx].SpriteStruct.x
mov edi, [ebx].SpriteStruct.y
invoke SpawnExplosionLight, esi, edi
mov i, 0
aoe_loop:
mov eax, i
cmp eax, spriteCount
jge aoe_check_player
mov ecx, i
cmp ecx, closestIdx
je next_aoe
mov eax, [entityType + ecx*4]
cmp eax, ENTITY_TYPE_GUARD
jne next_aoe
cmp [entityState + ecx*4], ENTITY_STATE_DEAD
je next_aoe
; EDX is volatile scratch for CDQ below, not a safe sprite pointer.
mov ebx, i
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList
mov eax, [ebx].SpriteStruct.x
sub eax, esi
cdq
xor eax, edx
sub eax, edx
cmp eax, 128
jg next_aoe
mov eax, [ebx].SpriteStruct.y
sub eax, edi
cdq
xor eax, edx
sub eax, edx
cmp eax, 128
jg next_aoe
; Within blast: deal 60 blast damage!
mov eax, [entityHealth + ecx*4]
sub eax, 60
jns @F
xor eax, eax
@@:
mov [entityHealth + ecx*4], eax
test eax, eax
jnz next_aoe
mov [entityState + ecx*4], ENTITY_STATE_DEAD
add playerScore, 150
next_aoe:
inc i
jmp aoe_loop
aoe_check_player:
; If player was standing too close to the barrel (< 96 units), player takes blast damage!
mov eax, playerX
sub eax, esi
cdq
xor eax, edx
sub eax, edx
cmp eax, 96
jg aoe_done
mov eax, playerY
sub eax, edi
cdq
xor eax, edx
sub eax, edx
cmp eax, 96
jg aoe_done
; Player blast damage
mov eax, playerHealth
sub eax, 25
jns @F
xor eax, eax
@@:
mov playerHealth, eax
test eax, eax
jnz aoe_done
mov isPlayerDead, 1
aoe_done:
pop edi
pop esi
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
; If in adventure mode, unarmed, or weapons disabled, skip weapon & combat stats
cmp gameMode, GAME_MODE_ADVENTURE
je hud_damage_flash
cmp enableWeapons, 0
je hud_damage_flash
cmp playerWeapon, WEAPON_NONE
je hud_damage_flash
; 1. Center Crosshair (subtle cross at 160, 120)
cmp showCrosshair, 0
je skip_crosshair
invoke DrawRect, 156, 120, 9, 1, 00FFFFFFh
invoke DrawRect, 160, 116, 1, 9, 00FFFFFFh
invoke DrawRect, 160, 120, 1, 1, 0000FF00h
skip_crosshair:
; 2. Render Active Weapon at bottom center
cmp playerWeapon, WEAPON_KNIFE
je render_knife_weapon
cmp playerWeapon, WEAPON_SHOTGUN
je render_shotgun_weapon
jmp render_pistol_weapon
render_knife_weapon:
; Knife Combat Animation: thrusts forward when cooldown active
mov gunY, 190
cmp playerFlashTimer, 0
je draw_knife
sub gunY, 12 ; thrust forward!
draw_knife:
; Silver steel blade
invoke DrawRect, 166, gunY, 6, 26, 00D0D0D0h
invoke DrawRect, 168, gunY, 2, 26, 00FFFFFFh ; shine
invoke DrawRect, 167, gunY - 4, 4, 4, 00B0B0B0h ; blade tip
invoke DrawRect, 168, gunY - 7, 2, 3, 00E0E0E0h
; Brass crossguard
mov eax, gunY
add eax, 26
invoke DrawRect, 160, eax, 18, 5, 00D4AF37h
; Wooden grip
mov eax, gunY
add eax, 31
invoke DrawRect, 164, eax, 10, 22, 00502A10h
jmp hud_stats_draw
render_shotgun_weapon:
; Heavy Pump Shotgun
mov gunY, 188
cmp playerFlashTimer, 0
je draw_shotgun_barrels
add gunY, 4 ; heavy recoil!
; Massive dual blast flash!
invoke DrawRect, 146, 155, 28, 22, 00FFAA00h
invoke DrawRect, 150, 158, 20, 16, 00FFFF00h
invoke DrawRect, 154, 162, 12, 10, 00FFFFFFh
draw_shotgun_barrels:
; Twin heavy steel barrels
invoke DrawRect, 148, gunY, 10, 32, 002B2B2Bh
invoke DrawRect, 150, gunY, 6, 32, 00484848h
invoke DrawRect, 162, gunY, 10, 32, 002B2B2Bh
invoke DrawRect, 164, gunY, 6, 32, 00484848h
; Barrel rib connector
invoke DrawRect, 158, gunY, 4, 32, 00222222h
; Wooden pump forend
mov eax, gunY
add eax, 20
invoke DrawRect, 144, eax, 32, 18, 00603818h
invoke DrawRect, 146, eax, 28, 14, 007A4820h
jmp hud_stats_draw
render_pistol_weapon:
mov gunY, 192
cmp playerFlashTimer, 0
je draw_pistol_barrel
add gunY, 3 ; recoil offset
; 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_pistol_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
hud_stats_draw:
; 3. Health Bar at top-left: X=8, Y=8
invoke DrawString8x8, 8, 8, OFFSET strHpLabel, COLOR_GREEN
invoke DrawNumber8x8, 34, 8, playerHealth, COLOR_WHITE
invoke DrawRect, 66, 8, 50, 8, 00441010h
invoke DrawRectOutline, 66, 8, 50, 8, COLOR_WHITE
mov eax, playerHealth
imul eax, 48
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, 48
jle @F
mov barW, 48
@@:
mov edx, 0000CC33h ; Green
cmp playerHealth, 30
jg @F
mov edx, 00FF2222h ; Red warning
@@:
invoke DrawRect, 67, 9, barW, 6, edx
hud_ammo_draw:
; 4. Ammo Display: X=126, Y=8
invoke DrawString8x8, 126, 8, OFFSET strAmmoLabel, COLOR_YELLOW
invoke DrawNumber8x8, 168, 8, playerAmmo, COLOR_YELLOW
; 5. Keys: X=204, Y=8 (if collected)
cmp playerKeys, 0
jle hud_score_draw
invoke DrawString8x8, 204, 8, OFFSET strKeysLabel, COLOR_MAGENTA
invoke DrawNumber8x8, 238, 8, playerKeys, COLOR_MAGENTA
hud_score_draw:
; 6. Score Display: only in Debug HUD style
cmp hudStyle, HUD_STYLE_CLEAN
je draw_weapon_badge
invoke DrawString8x8, 8, 20, OFFSET strScoreLabel, COLOR_CYAN
invoke DrawNumber8x8, 58, 20, playerScore, COLOR_CYAN
draw_weapon_badge:
; 7. Active Weapon Badge at bottom-right corner: X=228, Y=222
invoke DrawRect, 228, 222, 88, 14, 00141414h
invoke DrawRectOutline, 228, 222, 88, 14, 00444444h
cmp playerWeapon, WEAPON_KNIFE
jne @F
invoke DrawString8x8, 233, 225, OFFSET strWeaponKnife, COLOR_CYAN
jmp draw_weapon_slots
@@:
cmp playerWeapon, WEAPON_SHOTGUN
jne @F
invoke DrawString8x8, 233, 225, OFFSET strWeaponShotgun, 00FFAA00h
jmp draw_weapon_slots
@@:
invoke DrawString8x8, 233, 225, OFFSET strWeaponPistol, COLOR_YELLOW
draw_weapon_slots:
; Draw small weapon slot indicator boxes: [1] [2] [3]
invoke DrawRectOutline, 287, 225, 6, 8, 00555555h
invoke DrawRectOutline, 295, 225, 6, 8, 00555555h
invoke DrawRectOutline, 303, 225, 6, 8, 00555555h
; Highlight active slot
mov eax, playerWeapon
imul eax, 8
add eax, 287
invoke DrawRectOutline, eax, 225, 6, 8, COLOR_YELLOW
hud_damage_flash:
; 8. 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:
; 9. 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
; ------------------------------------------------------------------------------
; RenderPauseMenu: Renders stylish retro pause overlay when isGamePaused != 0
; ------------------------------------------------------------------------------
RenderPauseMenu PROC uses ebx ecx edx
cmp isGamePaused, 0
je pause_menu_done
; Centered semi-transparent panel 220x112 at (50, 64)
invoke DrawRect, 50, 64, 220, 112, 00141416h
invoke DrawRectOutline, 50, 64, 220, 112, COLOR_CYAN
invoke DrawRectOutline, 52, 66, 216, 108, COLOR_WHITE
; Header
invoke DrawString8x8, 112, 72, OFFSET strPauseTitle, COLOR_YELLOW
; Menu Options
invoke DrawString8x8, 64, 88, OFFSET strPauseOpt1, COLOR_GREEN
invoke DrawString8x8, 64, 102, OFFSET strPauseOpt2, COLOR_WHITE
invoke DrawString8x8, 64, 116, OFFSET strPauseOpt3, COLOR_CYAN
invoke DrawString8x8, 64, 130, OFFSET strPauseOpt4, COLOR_YELLOW
invoke DrawString8x8, 64, 146, OFFSET strPauseOpt5, 00FF4444h
pause_menu_done:
ret
RenderPauseMenu ENDP