; The visible people and the user's personality records share the same positions. InitNpcLife3D PROC uses ebx esi edi xor esi,esi mov npcLifeClock3D,0 mov npcSelected3D,-1 npc_life_init: imul ebx,esi,SIZEOF NpcLife3D mov eax,npcInitialIdentity3D[esi*4] mov npcLife3D[ebx].identity,eax mov npcLife3D[ebx].state,NPC_STAND mov eax,esi imul eax,900 add eax,1800 mov npcLife3D[ebx].remaining,eax mov npcLife3D[ebx].routeStep,0 mov npcLife3D[ebx].pathCount,0 mov npcLife3D[ebx].remainder,0 mov npcLife3D[ebx].poseClock,0 mov npcWalkDistance3D[esi*4],0 mov npcWalkVelocityX3D[esi*4],0 mov npcWalkVelocityY3D[esi*4],0 mov npcRise3D[esi*4],0 mov npcSitTime3D[esi*4],0 mov eax,npcStartDest3D[esi*4] mov npcLife3D[ebx].destination,eax push eax invoke NpcEntity3D,esi mov edi,eax pop eax mov ecx,npcDestX3D[eax*4] mov [edi].NpcMindEntity.posX,ecx mov ecx,npcDestY3D[eax*4] mov [edi].NpcMindEntity.posY,ecx npc_life_init_next: inc esi cmp esi,NPC_LIFE_COUNT jb npc_life_init call InitNpcVisits3D mov npcLifeReady3D,1 ret InitNpcLife3D ENDP ; BFS visits each tile at most once. Body clearance includes furniture and the stage. NpcTryCell3D PROC uses ebx esi edi cell:DWORD,parent:DWORD local startX:DWORD,startY:DWORD,endX:DWORD,endY:DWORD,probeX:DWORD mov esi,cell cmp esi,NPC_PATH_CAP jae npc_cell_done cmp npcParents3D[esi*4],-1 jne npc_cell_done mov eax,esi and eax,31 shl eax,16 add eax,FP_HALF mov edx,esi shr edx,5 shl edx,16 add edx,FP_HALF mov endX,eax mov endY,edx invoke CanWalk3D,eax,edx test eax,eax jz npc_cell_done ; Free tile centres do not imply a free edge beside a chair or thin divider. mov eax,parent mov edx,eax and eax,31 shl eax,16 add eax,FP_HALF mov startX,eax shr edx,5 shl edx,16 add edx,FP_HALF mov startY,edx mov edi,1 npc_cell_edge: mov eax,endX sub eax,startX imul eax,edi sar eax,2 add eax,startX mov probeX,eax mov eax,endY sub eax,startY imul eax,edi sar eax,2 add eax,startY invoke CanWalk3D,probeX,eax test eax,eax jz npc_cell_done inc edi cmp edi,4 jb npc_cell_edge mov eax,parent mov npcParents3D[esi*4],eax mov ebx,npcSearchTail3D cmp ebx,NPC_PATH_CAP jae npc_cell_done mov npcQueue3D[ebx*4],esi inc npcSearchTail3D npc_cell_done: ret NpcTryCell3D ENDP NpcPlanPath3D PROC uses ebx esi edi slot:DWORD,destination:DWORD local firstCell:DWORD,currentCell:DWORD,pathBase:DWORD cmp slot,NPC_LIFE_COUNT jae npc_plan_fail cmp destination,NPC_DEST_COUNT jae npc_plan_fail imul ebx,slot,SIZEOF NpcLife3D mov npcLife3D[ebx].pathCount,0 invoke NpcEntity3D,slot mov esi,eax mov eax,[esi].NpcMindEntity.posY sar eax,16 shl eax,5 mov ecx,[esi].NpcMindEntity.posX sar ecx,16 add eax,ecx cmp eax,NPC_PATH_CAP jae npc_plan_fail mov firstCell,eax mov ecx,destination mov eax,npcDestY3D[ecx*4] sar eax,16 shl eax,5 mov edx,npcDestX3D[ecx*4] sar edx,16 add eax,edx mov npcSearchGoal3D,eax lea edi,npcParents3D mov ecx,NPC_PATH_CAP mov eax,-1 rep stosd mov eax,firstCell mov npcParents3D[eax*4],eax mov npcQueue3D,eax mov npcSearchHead3D,0 mov npcSearchTail3D,1 npc_plan_search: mov eax,npcSearchHead3D cmp eax,npcSearchTail3D jae npc_plan_fail mov esi,npcQueue3D[eax*4] inc npcSearchHead3D cmp esi,npcSearchGoal3D je npc_plan_found mov eax,esi and eax,31 test eax,eax jz @F lea eax,[esi-1] invoke NpcTryCell3D,eax,esi @@: mov eax,esi and eax,31 cmp eax,31 je @F lea eax,[esi+1] invoke NpcTryCell3D,eax,esi @@: cmp esi,32 jb @F lea eax,[esi-32] invoke NpcTryCell3D,eax,esi @@: cmp esi,NPC_PATH_CAP-32 jae npc_plan_search lea eax,[esi+32] invoke NpcTryCell3D,eax,esi jmp npc_plan_search npc_plan_found: mov eax,slot imul eax,NPC_PATH_CAP*4 add eax,OFFSET npcPaths3D mov pathBase,eax xor edi,edi npc_plan_reverse: cmp esi,firstCell je npc_plan_success cmp edi,NPC_PATH_CAP jae npc_plan_fail mov edx,pathBase mov [edx+edi*4],esi inc edi mov esi,npcParents3D[esi*4] jmp npc_plan_reverse npc_plan_success: mov npcLife3D[ebx].pathCount,edi mov eax,destination mov npcLife3D[ebx].destination,eax mov eax,1 ret npc_plan_fail: xor eax,eax ret NpcPlanPath3D ENDP NpcCanStep3D PROC uses ebx esi slot:DWORD,px:DWORD,py:DWORD invoke CanWalk3D,px,py test eax,eax jz npc_space_done mov eax,px sub eax,playerX jns @F neg eax @@: cmp eax,FP_1*3/10 jae npc_space_people mov eax,py sub eax,playerY jns @F neg eax @@: cmp eax,FP_1*3/10 jb npc_space_blocked npc_space_people: xor esi,esi npc_space_person: cmp esi,slot je npc_space_next imul ebx,esi,SIZEOF NpcLife3D cmp npcLife3D[ebx].state,NPC_AWAY je npc_space_next invoke NpcEntity3D,esi mov ebx,eax mov eax,px sub eax,[ebx].NpcMindEntity.posX jns @F neg eax @@: cmp eax,FP_1*2/5 jae npc_space_next mov eax,py sub eax,[ebx].NpcMindEntity.posY jns @F neg eax @@: cmp eax,FP_1*2/5 jb npc_space_blocked npc_space_next: inc esi cmp esi,NPC_LIFE_COUNT jb npc_space_person mov eax,1 ret npc_space_blocked: xor eax,eax npc_space_done: ret NpcCanStep3D ENDP ; Millisecond movement, carrying the remainder and consuming whole route cells. NpcMove3D PROC uses ebx esi edi slot:DWORD,milliseconds:DWORD local budget:DWORD,wx:DWORD,wy:DWORD,mind:DWORD,pathBase:DWORD local oldX:DWORD,oldY:DWORD imul ebx,slot,SIZEOF NpcLife3D invoke NpcEntity3D,slot mov esi,eax mov mind,esi mov eax,[esi].NpcMindEntity.posX mov oldX,eax mov eax,[esi].NpcMindEntity.posY mov oldY,eax mov eax,slot imul eax,NPC_PATH_CAP*4 add eax,OFFSET npcPaths3D mov pathBase,eax mov eax,milliseconds mov ecx,slot mov ecx,npcVisitSpeed3D[ecx*4] mul ecx add eax,npcLife3D[ebx].remainder adc edx,0 mov ecx,1000 div ecx mov npcLife3D[ebx].remainder,edx mov budget,eax npc_move_cell: mov eax,npcLife3D[ebx].pathCount test eax,eax jz npc_move_arrived dec eax mov edx,pathBase mov edi,[edx+eax*4] mov eax,edi and eax,31 shl eax,16 add eax,FP_HALF mov wx,eax mov eax,edi shr eax,5 shl eax,16 add eax,FP_HALF mov wy,eax mov esi,mind mov eax,wx sub eax,[esi].NpcMindEntity.posX mov edi,1 test eax,eax jns @F neg eax mov edi,-1 @@: test eax,eax jz npc_move_y cmp eax,budget jbe @F mov eax,budget @@: sub budget,eax imul eax,edi add eax,[esi].NpcMindEntity.posX mov wx,eax invoke NpcCanStep3D,slot,wx,[esi].NpcMindEntity.posY test eax,eax jz npc_move_blocked mov eax,wx mov [esi].NpcMindEntity.posX,eax npc_move_y: mov eax,wy sub eax,[esi].NpcMindEntity.posY mov edi,1 test eax,eax jns @F neg eax mov edi,-1 @@: cmp eax,budget jbe @F mov eax,budget @@: sub budget,eax imul eax,edi add eax,[esi].NpcMindEntity.posY mov wy,eax invoke NpcCanStep3D,slot,[esi].NpcMindEntity.posX,wy test eax,eax jz npc_move_blocked mov eax,wy mov [esi].NpcMindEntity.posY,eax mov edx,pathBase mov eax,npcLife3D[ebx].pathCount dec eax mov edi,[edx+eax*4] mov eax,[esi].NpcMindEntity.posX sub eax,FP_HALF sar eax,16 mov edx,[esi].NpcMindEntity.posY sub edx,FP_HALF sar edx,16 shl edx,5 add eax,edx cmp eax,edi jne npc_move_done ; Integer cell membership is insufficient: finish at the exact cell centre. mov eax,edi and eax,31 shl eax,16 add eax,FP_HALF cmp eax,[esi].NpcMindEntity.posX jne npc_move_done mov eax,edi shr eax,5 shl eax,16 add eax,FP_HALF cmp eax,[esi].NpcMindEntity.posY jne npc_move_done dec npcLife3D[ebx].pathCount cmp budget,0 jne npc_move_cell cmp npcLife3D[ebx].pathCount,0 jne npc_move_done npc_move_arrived: invoke NpcArrive3D,slot jmp npc_move_done npc_move_blocked: mov eax,slot add npcVisitBlocked3D[eax*4],250 cmp npcVisitBlocked3D[eax*4],2000 jae npc_move_abandon mov npcLife3D[ebx].state,NPC_STAND mov npcVisitActivity3D[eax*4],NV_YIELD imul eax,23 add eax,200 mov npcLife3D[ebx].remaining,eax jmp npc_move_done npc_move_abandon: invoke NpcReleaseStation3D,slot mov npcLife3D[ebx].state,NPC_STAND mov npcLife3D[ebx].remaining,1000 mov npcLife3D[ebx].pathCount,0 mov eax,slot mov npcVisitActivity3D[eax*4],NV_IDLE npc_move_done: ; Advance gait by successful displacement only, including partial blocked steps. mov esi,mind mov ebx,slot mov eax,[esi].NpcMindEntity.posX sub eax,oldX mov ecx,eax mov eax,[esi].NpcMindEntity.posY sub eax,oldY mov edi,eax or eax,ecx jz npc_move_no_stride mov npcVisitBlocked3D[ebx*4],0 mov npcWalkVelocityX3D[ebx*4],ecx mov npcWalkVelocityY3D[ebx*4],edi mov eax,ecx cdq xor eax,edx sub eax,edx mov ecx,eax mov eax,edi cdq xor eax,edx sub eax,edx add eax,ecx add eax,npcWalkDistance3D[ebx*4] xor edx,edx mov ecx,FP_1*3/4 div ecx mov npcWalkDistance3D[ebx*4],edx imul eax,ebx,SIZEOF NpcLife3D cmp npcLife3D[eax].state,NPC_WALK jne npc_move_no_stride ret npc_move_no_stride: mov eax,slot mov npcWalkVelocityX3D[eax*4],0 mov npcWalkVelocityY3D[eax*4],0 ret NpcMove3D ENDP UpdateNpcLife3D PROC uses ebx esi edi local milliseconds:DWORD cmp npcLifeReady3D,1 jne npc_update_done cmp hallLevel3D,0 jne npc_update_done cmp engineMode,MODE_WALK jne npc_update_done mov eax,deltaTime3D cmp eax,250 jbe @F mov eax,250 @@: mov milliseconds,eax add npcLifeClock3D,eax xor esi,esi npc_update_person: imul ebx,esi,SIZEOF NpcLife3D mov eax,milliseconds add npcLife3D[ebx].poseClock,eax sub npcRemarkTime3D[esi*4],eax jns @F mov npcRemarkTime3D[esi*4],0 @@: cmp npcLife3D[ebx].state,NPC_SIT jne npc_update_seat_ready mov ecx,npcSitTime3D[esi*4] add ecx,eax cmp ecx,800 jbe @F mov ecx,800 @@: mov npcSitTime3D[esi*4],ecx npc_update_seat_ready: cmp npcLife3D[ebx].state,NPC_WALK jne npc_update_wait cmp npcRise3D[esi*4],0 je npc_update_move sub npcRise3D[esi*4],eax ja npc_update_sync mov npcRise3D[esi*4],0 jmp npc_update_sync npc_update_move: invoke NpcMove3D,esi,milliseconds jmp npc_update_sync npc_update_wait: sub npcLife3D[ebx].remaining,eax ja npc_update_sync invoke NpcAdvanceVisit3D,esi npc_update_sync: invoke NpcEntity3D,esi mov edi,eax mov eax,npcLife3D[ebx].state mov [edi].NpcMindEntity.state,eax mov eax,npcLife3D[ebx].remaining mov [edi].NpcMindEntity.stateTimer,eax ; Keep the old public position array coherent for existing world checks. cmp esi,6 jae npc_update_sync_next imul ecx,esi,SIZEOF CityProp3D mov eax,[edi].NpcMindEntity.posX mov hallGuests3D[ecx].posX,eax mov eax,[edi].NpcMindEntity.posY mov hallGuests3D[ecx].posY,eax npc_update_sync_next: inc esi cmp esi,NPC_LIFE_COUNT jb npc_update_person npc_update_done: ret UpdateNpcLife3D ENDP RenderNpcLife3D PROC uses ebx esi edi local castId:DWORD,castAction:DWORD,castClock:DWORD,slot:DWORD cmp npcLifeReady3D,1 jne npc_draw_done xor esi,esi npc_draw_depth: invoke NpcEntity3D,esi mov ebx,eax invoke ProjectCityPoint3D,[ebx].NpcMindEntity.posX,[ebx].NpcMindEntity.posY mov npcDrawDepth3D[esi*4],edx mov npcDrawOrder3D[esi*4],esi inc esi cmp esi,NPC_LIFE_COUNT jb npc_draw_depth xor esi,esi npc_draw_sort: lea edi,[esi+1] npc_draw_compare: mov eax,npcDrawOrder3D[esi*4] mov ebx,npcDrawDepth3D[eax*4] mov ecx,npcDrawOrder3D[edi*4] cmp ebx,npcDrawDepth3D[ecx*4] jge @F mov npcDrawOrder3D[esi*4],ecx mov npcDrawOrder3D[edi*4],eax @@: inc edi cmp edi,NPC_LIFE_COUNT jb npc_draw_compare inc esi cmp esi,NPC_LIFE_COUNT-1 jb npc_draw_sort xor esi,esi npc_draw_person: mov eax,npcDrawOrder3D[esi*4] imul ebx,eax,SIZEOF NpcLife3D cmp npcLife3D[ebx].state,NPC_AWAY je npc_draw_next invoke NpcEntity3D,eax mov edi,eax mov eax,[edi].NpcMindEntity.posX mov npcSprite3D.posX,eax mov eax,[edi].NpcMindEntity.posY mov npcSprite3D.posY,eax mov eax,npcLife3D[ebx].identity mov eax,npcGuestAppearance3D[eax*4] mov castId,eax mov castAction,CAST_IDLE mov eax,npcLife3D[ebx].poseClock mov castClock,eax mov eax,npcDrawOrder3D[esi*4] mov slot,eax invoke NpcResolveVisitPose3D,eax mov castAction,eax cmp npcLife3D[ebx].state,NPC_SIT jne npc_draw_walk mov eax,slot mov eax,npcSitTime3D[eax*4] cmp eax,800 jae npc_draw_sprite mov castClock,eax mov castAction,CAST_SIT_DOWN jmp npc_draw_sprite npc_draw_walk: cmp npcLife3D[ebx].state,NPC_WALK jne npc_draw_sprite mov ebx,slot cmp npcRise3D[ebx*4],0 je npc_draw_moving mov castAction,CAST_STAND_UP mov eax,800 sub eax,npcRise3D[ebx*4] mov castClock,eax jmp npc_draw_sprite npc_draw_moving: mov eax,npcWalkVelocityX3D[ebx*4] or eax,npcWalkVelocityY3D[ebx*4] jz npc_draw_sprite mov castAction,CAST_WALK mov eax,npcWalkDistance3D[ebx*4] mov castClock,eax npc_draw_sprite: mov ebx,slot invoke ConfigureCharacter3D,OFFSET npcSprite3D,castId,castAction,castClock,npcWalkVelocityX3D[ebx*4],npcWalkVelocityY3D[ebx*4] invoke DrawCityObject3D,OFFSET npcSprite3D,0 invoke NpcDrawRemark3D,slot npc_draw_next: inc esi cmp esi,NPC_LIFE_COUNT jb npc_draw_person npc_draw_done: ret RenderNpcLife3D ENDP CheckNpcInteractions3D PROC uses ebx esi edi local distance:DWORD,nearest:DWORD,deltaX:DWORD,deltaY:DWORD cmp npcLifeReady3D,1 jne npc_interact_done cmp hallLevel3D,0 jne npc_interact_done cmp interactTargetId,0 jne npc_interact_done mov npcSelected3D,-1 mov nearest,165000 xor esi,esi npc_interact_person: imul ebx,esi,SIZEOF NpcLife3D cmp npcLife3D[ebx].state,NPC_AWAY je npc_interact_next invoke NpcEntity3D,esi mov edi,eax mov eax,[edi].NpcMindEntity.posX sub eax,playerX sar eax,8 mov deltaX,eax imul eax,eax mov distance,eax mov eax,[edi].NpcMindEntity.posY sub eax,playerY sar eax,8 mov deltaY,eax imul eax,eax add eax,distance cmp eax,nearest jae npc_interact_next mov distance,eax mov eax,playerDirX sar eax,8 imul eax,deltaX mov edx,playerDirY sar edx,8 imul edx,deltaY add eax,edx cmp eax,6000 jl npc_interact_next ; Short, bounded line-of-sight test prevents talking through a wall. mov ebx,1 npc_interact_ray: mov eax,deltaX imul eax,ebx shl eax,5 add eax,playerX sar eax,16 mov edx,deltaY imul edx,ebx shl edx,5 add edx,playerY sar edx,16 cmp eax,32 jae npc_interact_next cmp edx,32 jae npc_interact_next shl edx,5 add edx,eax mov ecx,worldMap3D cmp BYTE PTR [ecx+edx],0 jne npc_interact_next inc ebx cmp ebx,8 jb npc_interact_ray mov eax,distance mov nearest,eax mov npcSelected3D,esi npc_interact_next: inc esi cmp esi,NPC_LIFE_COUNT jb npc_interact_person cmp npcSelected3D,-1 je npc_interact_done mov interactTargetId,INTERACT_NPC mov interactPromptPtr,OFFSET npcTalkPrompt3D npc_interact_done: ret CheckNpcInteractions3D ENDP include src_graphics\npc_visits.inc