KAZIK/src_graphics/hell_fps_combat.inc
Игорь Федосеев ce5121debd feat: add native hell expedition
Add a painting entrance, Freedoom combat and Doom-style HUD.

Persist the one-time clear reward in signed saves without changing file sizes. Include native regression checks and synchronized game/launcher builds.
2026-11-01 22:29:09 +03:00

764 lines
16 KiB
PHP

; All positions/velocities are signed 16.16; bounded pools and fixed ticks.
HFDistance PROC x0:DWORD,y0:DWORD,x1:DWORD,y1:DWORD
mov eax,x0
sub eax,x1
cdq
xor eax,edx
sub eax,edx
mov ecx,eax
mov eax,y0
sub eax,y1
cdq
xor eax,edx
sub eax,edx
cmp eax,ecx
jae @F
mov eax,ecx
@@:
ret
HFDistance ENDP
HFPlayerCanWalk PROC uses esi edi px:DWORD,py:DWORD
invoke CanWalk3D,px,py
test eax,eax
jz hf_player_walk_done
mov esi,OFFSET hfEnemies
mov edi,HF_ENEMIES
hf_player_body:
cmp [esi].HFEnemy.hp,0
jle hf_player_body_next
invoke HFDistance,px,py,[esi].HFEnemy.x,[esi].HFEnemy.y
cmp eax,FP_1*9/20
jb hf_player_body_block
hf_player_body_next:
add esi,SIZEOF HFEnemy
dec edi
jnz hf_player_body
mov eax,1
ret
hf_player_body_block:
xor eax,eax
hf_player_walk_done:
ret
HFPlayerCanWalk ENDP
HFLineClear PROC uses ebx esi edi x0:DWORD,y0:DWORD,x1:DWORD,y1:DWORD
local stepX:DWORD,stepY:DWORD,rx:DWORD,ry:DWORD
invoke HFDistance,x0,y0,x1,y1
shr eax,12 ; quarter of a collision radius per sample, <= 1/16 tile
inc eax
mov ebx,eax
mov eax,x1
sub eax,x0
cdq
idiv ebx
mov stepX,eax
mov eax,y1
sub eax,y0
cdq
idiv ebx
mov stepY,eax
mov esi,x0
mov edi,y0
hf_los_step:
add esi,stepX
add edi,stepY
mov eax,esi
sar eax,16
cmp eax,32
jae hf_los_blocked
mov ecx,edi
sar ecx,16
cmp ecx,32
jae hf_los_blocked
shl ecx,5
add eax,ecx
cmp hfMap[eax],0
jne hf_los_blocked
dec ebx
jnz hf_los_step
mov eax,1
ret
hf_los_blocked:
xor eax,eax
ret
HFLineClear ENDP
HFDamagePlayer PROC uses ebx amount:DWORD
cmp hfState,0
jne hf_damage_done
mov ebx,amount
mov eax,ebx
shr eax,1
cmp eax,hfArmor
jbe @F
mov eax,hfArmor
@@:
sub hfArmor,eax
sub ebx,eax
sub hfHealth,ebx
mov hfHurt,10
invoke HFPlaySound,2
cmp hfHealth,0
jg hf_damage_done
mov hfHealth,0
mov hfState,1
mov hfFireHeld,0
hf_damage_done:
ret
HFDamagePlayer ENDP
HFDamageEnemy PROC uses esi enemy:DWORD,amount:DWORD
mov esi,enemy
cmp [esi].HFEnemy.hp,0
jle hf_enemy_damage_done
mov eax,amount
sub [esi].HFEnemy.hp,eax
mov [esi].HFEnemy.awake,1
mov [esi].HFEnemy.pose,6
mov [esi].HFEnemy.timer,4
cmp [esi].HFEnemy.hp,0
jg hf_enemy_damage_done
mov [esi].HFEnemy.hp,0
mov [esi].HFEnemy.pose,7
mov [esi].HFEnemy.timer,0
inc hfKills
hf_enemy_damage_done:
ret
HFDamageEnemy ENDP
HFFire PROC uses esi edi ebx
local pellet:DWORD,angle:DWORD,rx:DWORD,ry:DWORD,stepX:DWORD,stepY:DWORD,steps:DWORD
cmp hfCooldown,0
jne hf_fire_done
cmp hfWeapon,1
je hf_fire_shotgun
cmp hfBullets,0
je hf_fire_done
dec hfBullets
mov hfCooldown,9
mov pellet,1
invoke HFPlaySound,4
jmp hf_fire_begin
hf_fire_shotgun:
cmp hfShells,0
je hf_fire_done
dec hfShells
mov hfCooldown,29
mov pellet,HF_SHOT_PELLETS
invoke HFPlaySound,0
hf_fire_begin:
mov hfFlash,4
hf_fire_pellet:
mov eax,playerAngle
cmp hfWeapon,0
je @F
mov ecx,pellet
add eax,hfPelletSpread[ecx*4-4]
@@:
and eax,511
mov angle,eax
mov ecx,tableCos[eax*4]
sar ecx,3
mov stepX,ecx
mov ecx,tableSin[eax*4]
sar ecx,3
mov stepY,ecx
mov eax,playerX
mov rx,eax
mov eax,playerY
mov ry,eax
mov steps,192
hf_ray_step:
mov eax,stepX
add rx,eax
mov eax,stepY
add ry,eax
mov eax,rx
sar eax,16
cmp eax,32
jae hf_ray_end
mov ecx,ry
sar ecx,16
cmp ecx,32
jae hf_ray_end
shl ecx,5
add eax,ecx
cmp hfMap[eax],0
jne hf_ray_end
mov esi,OFFSET hfEnemies
mov ebx,HF_ENEMIES
hf_ray_enemy:
cmp [esi].HFEnemy.hp,0
jle hf_ray_next_enemy
invoke HFDistance,rx,ry,[esi].HFEnemy.x,[esi].HFEnemy.y
cmp eax,FP_1*7/20
ja hf_ray_next_enemy
invoke RandomRange3D,9
add eax,8
invoke HFDamageEnemy,esi,eax
jmp hf_ray_end
hf_ray_next_enemy:
add esi,SIZEOF HFEnemy
dec ebx
jnz hf_ray_enemy
dec steps
jnz hf_ray_step
hf_ray_end:
; Each pellet owns its own impact; stay just in front of a wall's depth.
mov ecx,pellet
dec ecx
imul ecx,SIZEOF HFImpact
mov eax,rx
sub eax,stepX
sub eax,stepX
mov hfImpacts[ecx].x,eax
mov eax,ry
sub eax,stepY
sub eax,stepY
mov hfImpacts[ecx].y,eax
mov hfImpacts[ecx].life,8
dec pellet
jnz hf_fire_pellet
hf_fire_done:
ret
HFFire ENDP
HFSpawnBolt PROC uses esi ebx x:DWORD,y:DWORD,damage:DWORD
local distance:DWORD
mov esi,OFFSET hfBolts
mov ebx,32
@@:
cmp [esi].HFBolt.life,0
je hf_bolt_slot
add esi,SIZEOF HFBolt
dec ebx
jnz @B
ret
hf_bolt_slot:
invoke HFDistance,x,y,playerX,playerY
cmp eax,FP_1/2
jae @F
mov eax,FP_1/2
@@:
mov distance,eax
mov eax,x
mov [esi].HFBolt.x,eax
mov eax,y
mov [esi].HFBolt.y,eax
mov eax,damage
mov [esi].HFBolt.damage,eax
mov [esi].HFBolt.life,210
mov eax,playerX
sub eax,x
mov ecx,FP_1/7
imul ecx
idiv distance
mov [esi].HFBolt.vx,eax
mov eax,playerY
sub eax,y
mov ecx,FP_1/7
imul ecx
idiv distance
mov [esi].HFBolt.vy,eax
invoke HFPlaySound,1
ret
HFSpawnBolt ENDP
; Breadth-first distance field permits monsters to navigate around room pillars.
HFBuildFlow PROC uses esi edi ebx
local head:DWORD,tail:DWORD,current:DWORD,neighbor:DWORD
cld
mov edi,OFFSET hfFlow
mov eax,0FFFFFFFFh
mov ecx,512
rep stosd
mov eax,playerY
sar eax,16
shl eax,5
mov ecx,playerX
sar ecx,16
add eax,ecx
mov hfFlow[eax*2],0
mov hfQueue,eax
mov head,0
mov tail,1
hf_flow_pop:
mov eax,head
cmp eax,tail
jae hf_flow_done
mov eax,hfQueue[eax*4]
mov current,eax
movzx ebx,hfFlow[eax*2]
inc ebx
xor esi,esi
hf_flow_neighbor:
mov eax,current
cmp esi,0
jne @F
dec eax
@@:
cmp esi,1
jne @F
inc eax
@@:
cmp esi,2
jne @F
sub eax,32
@@:
cmp esi,3
jne @F
add eax,32
@@:
cmp eax,1024
jae hf_flow_next
cmp hfMap[eax],0
jne hf_flow_next
cmp hfFlow[eax*2],0FFFFh
jne hf_flow_next
mov hfFlow[eax*2],bx
mov ecx,tail
mov hfQueue[ecx*4],eax
inc tail
hf_flow_next:
inc esi
cmp esi,4
jb hf_flow_neighbor
inc head
jmp hf_flow_pop
hf_flow_done:
ret
HFBuildFlow ENDP
HFMoveEnemy PROC uses esi edi ebx enemy:DWORD
local tx:DWORD,ty:DWORD,distance:DWORD,speed:DWORD,best:DWORD,cell:DWORD,nx:DWORD,ny:DWORD
mov esi,enemy
mov eax,playerX
mov tx,eax
mov eax,playerY
mov ty,eax
invoke HFLineClear,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY
test eax,eax
jnz hf_move_target
mov eax,[esi].HFEnemy.y
sar eax,16
shl eax,5
mov ecx,[esi].HFEnemy.x
sar ecx,16
add eax,ecx
mov cell,eax
movzx eax,hfFlow[eax*2]
mov best,eax
mov edi,-1
xor ebx,ebx
hf_move_flow:
mov eax,cell
cmp ebx,0
jne @F
dec eax
@@:
cmp ebx,1
jne @F
inc eax
@@:
cmp ebx,2
jne @F
sub eax,32
@@:
cmp ebx,3
jne @F
add eax,32
@@:
cmp eax,1024
jae hf_move_flow_next
movzx ecx,hfFlow[eax*2]
cmp ecx,best
jae hf_move_flow_next
mov best,ecx
mov edi,eax
hf_move_flow_next:
inc ebx
cmp ebx,4
jb hf_move_flow
cmp edi,-1
je hf_move_done
mov eax,edi
and eax,31
shl eax,16
add eax,FP_HALF
mov tx,eax
shr edi,5
shl edi,16
add edi,FP_HALF
mov ty,edi
hf_move_target:
invoke HFDistance,[esi].HFEnemy.x,[esi].HFEnemy.y,tx,ty
cmp eax,FP_1/8
jb hf_move_done
mov distance,eax
mov eax,[esi].HFEnemy.kind
mov eax,hfMonsterSpeed[eax*4]
mov speed,eax
mov eax,tx
sub eax,[esi].HFEnemy.x
imul speed
idiv distance
add eax,[esi].HFEnemy.x
mov nx,eax
invoke CanWalk3D,eax,[esi].HFEnemy.y
test eax,eax
jz @F
mov eax,nx
mov [esi].HFEnemy.x,eax
@@:
mov eax,ty
sub eax,[esi].HFEnemy.y
imul speed
idiv distance
add eax,[esi].HFEnemy.y
mov ny,eax
invoke CanWalk3D,[esi].HFEnemy.x,eax
test eax,eax
jz hf_move_done
mov eax,ny
mov [esi].HFEnemy.y,eax
hf_move_done:
ret
HFMoveEnemy ENDP
HFEnemiesTick PROC uses esi edi ebx
local distance:DWORD,visible:DWORD
mov esi,OFFSET hfEnemies
mov edi,HF_ENEMIES
hf_ai_enemy:
cmp [esi].HFEnemy.hp,0
jg hf_ai_alive
cmp [esi].HFEnemy.pose,11
jae hf_ai_next
inc [esi].HFEnemy.timer
cmp [esi].HFEnemy.timer,6
jb hf_ai_next
mov [esi].HFEnemy.timer,0
inc [esi].HFEnemy.pose
jmp hf_ai_next
hf_ai_alive:
cmp [esi].HFEnemy.cooldown,0
je @F
dec [esi].HFEnemy.cooldown
@@:
cmp [esi].HFEnemy.timer,0
je hf_ai_perceive
dec [esi].HFEnemy.timer
jnz hf_ai_next
cmp [esi].HFEnemy.pose,4
jne hf_ai_next
invoke HFLineClear,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY
test eax,eax
jz hf_ai_next
invoke HFDistance,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY
mov distance,eax
mov ebx,[esi].HFEnemy.kind
cmp eax,FP_1*7/10
jbe hf_ai_melee
cmp ebx,1
je hf_ai_next
cmp ebx,0
jne hf_ai_projectile
invoke RandomRange3D,4
test eax,eax
jz hf_ai_attack_pose
invoke HFDamagePlayer,6
jmp hf_ai_attack_pose
hf_ai_perceive:
invoke HFDistance,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY
mov distance,eax
mov visible,0
cmp eax,16*FP_1
ja hf_ai_awake
invoke HFLineClear,[esi].HFEnemy.x,[esi].HFEnemy.y,playerX,playerY
mov visible,eax
test eax,eax
jz hf_ai_awake
mov [esi].HFEnemy.awake,1
hf_ai_awake:
cmp [esi].HFEnemy.awake,0
je hf_ai_next
mov eax,hfTicks
shr eax,3
and eax,3
mov [esi].HFEnemy.pose,eax
cmp visible,0
je hf_ai_move
mov ebx,[esi].HFEnemy.kind
cmp [esi].HFEnemy.cooldown,0
jne hf_ai_spacing
cmp distance,FP_1*7/10
jbe hf_ai_windup
cmp ebx,1
je hf_ai_move
hf_ai_windup:
mov [esi].HFEnemy.pose,4
mov [esi].HFEnemy.timer,12
jmp hf_ai_next
hf_ai_projectile:
invoke HFSpawnBolt,[esi].HFEnemy.x,[esi].HFEnemy.y,hfMonsterDamage[ebx*4]
jmp hf_ai_attack_pose
hf_ai_melee:
invoke HFDamagePlayer,hfMonsterDamage[ebx*4]
hf_ai_attack_pose:
mov [esi].HFEnemy.pose,5
mov [esi].HFEnemy.timer,10
mov [esi].HFEnemy.cooldown,55
cmp ebx,3
jne hf_ai_next
mov [esi].HFEnemy.cooldown,18
jmp hf_ai_next
hf_ai_spacing:
cmp distance,FP_1*7/10
jb hf_ai_next
cmp ebx,1
je hf_ai_move
cmp distance,4*FP_1
jb hf_ai_next
hf_ai_move:
invoke HFMoveEnemy,esi
hf_ai_next:
add esi,SIZEOF HFEnemy
dec edi
jnz hf_ai_enemy
ret
HFEnemiesTick ENDP
HFBoltsTick PROC uses esi edi
mov esi,OFFSET hfBolts
mov edi,32
hf_projectile:
cmp [esi].HFBolt.life,0
je hf_projectile_next
dec [esi].HFBolt.life
mov eax,[esi].HFBolt.vx
add [esi].HFBolt.x,eax
mov eax,[esi].HFBolt.vy
add [esi].HFBolt.y,eax
invoke CanWalk3D,[esi].HFBolt.x,[esi].HFBolt.y
test eax,eax
jz hf_projectile_destroy
invoke HFDistance,[esi].HFBolt.x,[esi].HFBolt.y,playerX,playerY
cmp eax,FP_1*3/10
ja hf_projectile_next
invoke HFDamagePlayer,[esi].HFBolt.damage
hf_projectile_destroy:
mov [esi].HFBolt.life,0
hf_projectile_next:
add esi,SIZEOF HFBolt
dec edi
jnz hf_projectile
ret
HFBoltsTick ENDP
HFPickups PROC uses esi edi ebx
xor ebx,ebx
mov esi,OFFSET hfItemSeeds
hf_pickup:
cmp hfItemTaken[ebx*4],0
jne hf_pickup_next
invoke HFDistance,playerX,playerY,[esi],[esi+4]
cmp eax,FP_1*3/5
ja hf_pickup_next
mov edi,[esi+8]
cmp edi,0
jne hf_pick_armor
cmp hfHealth,100
jae hf_pickup_next
add hfHealth,25
cmp hfHealth,100
jbe hf_pick_commit
mov hfHealth,100
jmp hf_pick_commit
hf_pick_armor:
cmp edi,1
jne hf_pick_bullets
cmp hfArmor,100
jae hf_pickup_next
mov hfArmor,100
jmp hf_pick_commit
hf_pick_bullets:
cmp edi,2
jne hf_pick_shells
add hfBullets,40
jmp hf_pick_commit
hf_pick_shells:
cmp edi,3
jne hf_pick_shotgun
add hfShells,12
jmp hf_pick_commit
hf_pick_shotgun:
cmp edi,4
jne hf_pick_key
mov hfHasShotgun,1
mov hfWeapon,1
add hfShells,16
jmp hf_pick_commit
hf_pick_key:
mov hfKey,1
hf_pick_commit:
mov hfItemTaken[ebx*4],1
mov hfMessage,OFFSET hfPickupText
cmp edi,5
jne @F
mov hfMessage,OFFSET hfKeyFoundText
@@:
mov hfMessageTime,105
invoke HFPlaySound,3
hf_pickup_next:
add esi,12
inc ebx
cmp ebx,HF_ITEMS
jb hf_pickup
ret
HFPickups ENDP
HFUse PROC uses ebx esi
invoke HFDistance,playerX,playerY,FP_1*11/2,FP_1*25/2
cmp eax,FP_1*8/5
ja hf_use_secret_two
test hfDoorMask,1
jnz hf_use_secret_two
or hfDoorMask,1
mov hfMap[12*32+5],0
mov hfMap[12*32+6],0
jmp hf_use_secret
hf_use_secret_two:
invoke HFDistance,playerX,playerY,FP_1*45/2,27*FP_1
cmp eax,FP_1*8/5
ja hf_use_gate
test hfDoorMask,2
jnz hf_use_gate
or hfDoorMask,2
mov hfMap[26*32+22],0
mov hfMap[27*32+22],0
hf_use_secret:
inc hfSecrets
mov hfMessage,OFFSET hfSecretText
mov hfMessageTime,105
invoke HFPlaySound,5
ret
hf_use_gate:
invoke HFDistance,playerX,playerY,FP_1*45/2,7*FP_1
cmp eax,2*FP_1
ja hf_use_exit
cmp hfKey,0
jne hf_use_unlock
mov hfMessage,OFFSET hfNeedKeyText
mov hfMessageTime,105
ret
hf_use_unlock:
mov hfMap[6*32+22],0
mov hfMap[7*32+22],0
or hfDoorMask,4
mov hfMessage,OFFSET hfDoorText
mov hfMessageTime,105
invoke HFPlaySound,5
ret
hf_use_exit:
invoke HFDistance,playerX,playerY,28*FP_1,29*FP_1
cmp eax,2*FP_1
ja hf_use_done
mov esi,OFFSET hfEnemies
mov ebx,HF_ENEMIES
@@:
cmp [esi].HFEnemy.kind,3
jne hf_exit_next
cmp [esi].HFEnemy.hp,0
jg hf_exit_locked
hf_exit_next:
add esi,SIZEOF HFEnemy
dec ebx
jnz @B
mov hfState,2
mov hfFireHeld,0
mov hfMessageTime,0
cmp hfCompleted,0
jne hf_use_done
mov hfCompleted,1
mov hfAwarded,1
invoke ArtifactCredit3D,6660
call SaveProgress3D
ret
hf_exit_locked:
mov hfMessage,OFFSET hfExitText
mov hfMessageTime,105
hf_use_done:
ret
HFUse ENDP
HFTick PROC uses esi ecx
mov esi,OFFSET hfImpacts
mov ecx,HF_SHOT_PELLETS
hf_impact_tick:
cmp [esi].HFImpact.life,0
je @F
dec [esi].HFImpact.life
@@:
add esi,SIZEOF HFImpact
loop hf_impact_tick
inc hfTicks
cmp hfCooldown,0
je @F
dec hfCooldown
@@:
cmp hfFlash,0
je @F
dec hfFlash
@@:
cmp hfHurt,0
je @F
dec hfHurt
@@:
cmp hfMessageTime,0
je @F
dec hfMessageTime
@@:
mov eax,hfFireHeld
or eax,hfFirePending
or al,keys3D[20h]
jz @F
call HFFire
@@:
mov hfFirePending,0
cmp hfUsePending,0
je @F
mov hfUsePending,0
call HFUse
@@:
cmp hfState,0
jne hf_tick_done
mov eax,hfTicks
and eax,7
jnz @F
call HFBuildFlow
@@:
call HFEnemiesTick
call HFBoltsTick
cmp hfState,0
jne hf_tick_done
call HFPickups
; Central lava channel: visible floor patch exactly matches damage bounds.
mov eax,hfTicks
xor edx,edx
mov ecx,18
div ecx
test edx,edx
jnz hf_tick_done
cmp playerX,16*FP_1
jb hf_tick_done
cmp playerX,18*FP_1
jae hf_tick_done
cmp playerY,17*FP_1
jb hf_tick_done
cmp playerY,20*FP_1
jae hf_tick_done
invoke HFDamagePlayer,8
hf_tick_done:
ret
HFTick ENDP