; 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