; Hysteresis and a hold timer keep the shared doorway safe on both sides. UpdateBasementDoor3D PROC uses ebx local nearDoor:DWORD,rangeX:DWORD,rangeY:DWORD mov nearDoor,0 mov rangeX,FP_1*12/5 mov rangeY,FP_1*7/5 mov eax,basementDoorTarget3D or eax,basementDoorOpen3D or eax,basementWheelTime3D jz @F mov rangeX,3*FP_1 mov rangeY,FP_1*9/5 @@: cmp engineMode,MODE_WALK jne steel_approach_stairs cmp hallLevel3D,0 jne steel_door_clock mov eax,playerX sub eax,FP_1*289/10 cdq xor eax,edx sub eax,edx cmp eax,rangeX ja steel_door_clock mov eax,playerY sub eax,FP_1*105/10 cdq xor eax,edx sub eax,edx cmp eax,rangeY ja steel_door_clock jmp steel_request_open steel_approach_stairs: cmp engineMode,MODE_STAIRS jne steel_door_clock cmp stairRoute3D,1 jne steel_door_clock mov eax,5*FP_1 cmp rangeX,3*FP_1 jne @F mov eax,FP_1*13/2 @@: cmp playerX,eax ja steel_door_clock cmp playerY,FP_1*195/10 jb steel_door_clock cmp playerY,FP_1*235/10 ja steel_door_clock steel_request_open: mov nearDoor,1 mov basementDoorTarget3D,FP_1 mov basementDoorHold3D,1200 steel_door_clock: mov eax,animationClock3D cmp eax,interiorDoorClock3D je steel_update_done mov interiorDoorClock3D,eax mov ebx,deltaTime3D cmp ebx,100 jbe @F mov ebx,100 @@: cmp nearDoor,1 je steel_animate ; A manual E request outside the automatic opening radius still gets time. cmp basementDoorTarget3D,FP_1 jne steel_far_hold cmp basementDoorHold3D,0 jne steel_far_hold mov eax,basementDoorOpen3D or eax,basementWheelTime3D jnz steel_far_hold mov basementDoorHold3D,2000 steel_far_hold: cmp basementDoorHold3D,0 je steel_request_close mov eax,basementDoorHold3D sub eax,ebx jae @F xor eax,eax @@: mov basementDoorHold3D,eax jmp steel_animate steel_request_close: mov basementDoorTarget3D,0 steel_animate: cmp basementDoorTarget3D,0 je steel_close_leaf cmp basementWheelTime3D,700 jae steel_swing mov eax,700 sub eax,basementWheelTime3D cmp ebx,eax jb steel_wheel_partial sub ebx,eax mov basementWheelTime3D,700 jmp steel_swing steel_wheel_partial: add basementWheelTime3D,ebx jmp steel_update_done steel_swing: ; Opening: unlock first, then swing around the fixed hinge over 1000ms. imul eax,ebx,FP_1 xor edx,edx mov ecx,1000 div ecx add eax,basementDoorOpen3D cmp eax,FP_1 jbe @F mov eax,FP_1 @@: mov basementDoorOpen3D,eax jmp steel_update_done steel_close_leaf: cmp basementDoorOpen3D,0 je steel_lock_wheel ; Carry unused milliseconds into locking only after the leaf is fully shut. imul eax,basementDoorOpen3D,1000 add eax,FP_1-1 shr eax,16 cmp ebx,eax jb steel_close_partial sub ebx,eax mov basementDoorOpen3D,0 jmp steel_lock_wheel steel_close_partial: imul eax,ebx,FP_1 xor edx,edx mov ecx,1000 div ecx sub basementDoorOpen3D,eax jmp steel_update_done steel_lock_wheel: mov eax,basementWheelTime3D sub eax,ebx jae @F xor eax,eax @@: mov basementWheelTime3D,eax steel_update_done: ret UpdateBasementDoor3D ENDP ; The shared doorway occupies the overlapping landing in both local maps. StairDoorCanWalk3D PROC px:DWORD,py:DWORD cmp stairRoute3D,1 jne stair_steel_clear cmp px,FP_1*35/10 jb stair_steel_clear cmp px,FP_1*42/10 ja stair_steel_clear cmp py,FP_1*2025/100 jb stair_steel_blocked cmp py,FP_1*2175/100 jae stair_steel_blocked mov eax,px add eax,25*FP_1 mov edx,py sub edx,FP_1*21/2 invoke InteriorDoorCanWalk3D,eax,edx ret stair_steel_blocked: xor eax,eax ret stair_steel_clear: mov eax,1 ret StairDoorCanWalk3D ENDP RenderStairDoor3D PROC local oldX:DWORD,oldY:DWORD,oldEye:DWORD cmp stairRoute3D,1 jne stair_steel_done ; Fill the wider landing around the actual narrow doorway on the stair side. invoke DrawCityBoxSides3D,FP_1*375/100,4*FP_1,20*FP_1,FP_1*2025/100,FP_1*3/2,FP_1*37/10,92 invoke DrawCityBoxSides3D,FP_1*375/100,4*FP_1,FP_1*2175/100,23*FP_1,FP_1*3/2,FP_1*37/10,92 mov eax,playerX mov oldX,eax add playerX,25*FP_1 mov eax,playerY mov oldY,eax sub playerY,FP_1*21/2 mov eax,viewEye3D mov oldEye,eax sub viewEye3D,FP_1*3/2 invoke DrawCityVertical3D,0,FP_1*2838/100,9*FP_1,12*FP_1,-FP_1/10,FP_1*21/10,131 call DrawBasementLeaf3D mov eax,oldX mov playerX,eax mov eax,oldY mov playerY,eax mov eax,oldEye mov viewEye3D,eax stair_steel_done: ret RenderStairDoor3D ENDP ; Perspective-correct vertical plane with a rotating horizontal basis. The wheel ; is baked into offline texture phases, not rotated in screen space at runtime. DrawBasementLeaf3D PROC uses ebx esi edi local ux:DWORD,uy:DWORD,nx:DWORD,ny:DWORD,numerator:DWORD local depth:DWORD,texture:DWORD,sourceX:DWORD,top:DWORD,bottom:DWORD local stepY:DWORD,sourceY:DWORD,row:DWORD,column:DWORD,destination:DWORD invoke ResolveUpperMaterial3D,128 test eax,eax jz steel_leaf_done mov texture,eax mov eax,basementDoorOpen3D imul eax,128 shr eax,16 and eax,511 mov ecx,tableSin[eax*4] mov ux,ecx neg ecx mov ny,ecx mov ecx,tableCos[eax*4] mov uy,ecx mov nx,ecx mov eax,FP_1*289/10 sub eax,playerX imul nx shrd eax,edx,16 mov ebx,eax mov eax,FP_1*975/100 sub eax,playerY imul ny shrd eax,edx,16 add eax,ebx mov numerator,eax mov column,0 steel_leaf_column: mov ebx,column mov eax,cityRayX3D[ebx*4] imul nx shrd eax,edx,16 mov esi,eax mov eax,cityRayY3D[ebx*4] imul ny shrd eax,edx,16 add eax,esi mov ecx,eax cdq xor eax,edx sub eax,edx cmp eax,256 jb steel_leaf_next_column mov eax,numerator cdq shld edx,eax,16 shl eax,16 idiv ecx cmp eax,4096 jle steel_leaf_next_column cmp eax,zBuffer[ebx*4] jae steel_leaf_next_column cmp portalRendering3D,0 je @F cmp eax,portalClipDepth3D[ebx*4] jb steel_leaf_next_column @@: mov depth,eax imul cityRayX3D[ebx*4] shrd eax,edx,16 add eax,playerX sub eax,FP_1*289/10 imul ux shrd eax,edx,16 mov esi,eax mov eax,depth imul cityRayY3D[ebx*4] shrd eax,edx,16 add eax,playerY sub eax,FP_1*975/100 imul uy shrd eax,edx,16 add eax,esi test eax,eax js steel_leaf_next_column cmp eax,FP_1*3/2 jae steel_leaf_next_column imul eax,256 xor edx,edx mov ecx,FP_1*3/2 div ecx mov sourceX,eax mov eax,viewEye3D sub eax,FP_1*19/10 imul eax,SCREEN_H cdq idiv depth add eax,viewHorizon3D mov top,eax mov eax,viewEye3D imul eax,SCREEN_H cdq idiv depth add eax,viewHorizon3D mov bottom,eax sub eax,top jle steel_leaf_next_column mov ecx,eax mov eax,512*FP_1 xor edx,edx div ecx mov stepY,eax mov eax,top test eax,eax jns @F xor eax,eax @@: mov row,eax sub eax,top imul eax,stepY mov sourceY,eax cmp bottom,SCREEN_H jle @F mov bottom,SCREEN_H @@: mov eax,row cmp eax,bottom jge steel_leaf_next_column imul eax,SCREEN_W add eax,column shl eax,2 mov destination,eax steel_leaf_pixel: mov edi,destination mov eax,depth cmp eax,cityDepthBuffer3D[edi] ja steel_leaf_next_pixel mov eax,sourceY shr eax,16 cmp eax,512 jae steel_leaf_next_pixel shl eax,8 add eax,sourceX mov esi,texture mov eax,[esi+eax*4] cmp eax,80000000h jb steel_leaf_next_pixel and eax,00FFFFFFh mov screenBuffer[edi],eax mov eax,depth mov cityDepthBuffer3D[edi],eax steel_leaf_next_pixel: mov eax,stepY add sourceY,eax add destination,SCREEN_W*4 inc row mov eax,row cmp eax,bottom jb steel_leaf_pixel steel_leaf_next_column: inc column cmp column,SCREEN_W jb steel_leaf_column steel_leaf_done: ret DrawBasementLeaf3D ENDP