; 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 npcLife3D[ebx].identity,esi mov npcLife3D[ebx].state,NPC_STAND mov eax,esi imul eax,900 add eax,1800 mov npcLife3D[ebx].remaining,eax mov npcLife3D[ebx].routeStep,esi 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 eax,npcStartDest3D[esi*4] mov npcLife3D[ebx].destination,eax imul edi,esi,SIZEOF NpcMindEntity mov ecx,npcDestX3D[eax*4] mov casinoNpcs3D[edi].posX,ecx mov ecx,npcDestY3D[eax*4] mov casinoNpcs3D[edi].posY,ecx cmp eax,7 je @F cmp eax,8 jne npc_life_init_next @@: mov npcLife3D[ebx].state,NPC_SIT npc_life_init_next: inc esi cmp esi,NPC_LIFE_COUNT jb npc_life_init 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 cell:DWORD,parent: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 invoke CanWalk3D,eax,edx test eax,eax jz npc_cell_done 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 imul esi,slot,SIZEOF NpcMindEntity mov eax,casinoNpcs3D[esi].posY sar eax,16 shl eax,5 mov ecx,casinoNpcs3D[esi].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 imul ebx,esi,SIZEOF NpcMindEntity mov eax,px sub eax,casinoNpcs3D[ebx].posX jns @F neg eax @@: cmp eax,FP_1*2/5 jae npc_space_next mov eax,py sub eax,casinoNpcs3D[ebx].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 imul eax,slot,SIZEOF NpcMindEntity lea esi,casinoNpcs3D[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,55000 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: mov eax,npcLife3D[ebx].destination mov npcLife3D[ebx].state,NPC_STAND mov npcLife3D[ebx].remaining,3500 cmp eax,0 jne @F mov npcLife3D[ebx].state,NPC_AWAY mov npcLife3D[ebx].remaining,4500 jmp npc_move_done @@: cmp eax,7 je @F cmp eax,8 jne npc_move_done @@: mov npcLife3D[ebx].state,NPC_SIT mov npcLife3D[ebx].remaining,12000 jmp npc_move_done npc_move_blocked: mov npcLife3D[ebx].state,NPC_STAND mov npcLife3D[ebx].remaining,1000 mov npcLife3D[ebx].pathCount,0 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 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 npc_move_no_stride: ret NpcMove3D ENDP UpdateNpcLife3D PROC uses ebx esi edi local milliseconds:DWORD,destination:DWORD cmp npcLifeReady3D,1 jne npc_update_done cmp hallLevel3D,0 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 cmp npcLife3D[ebx].state,NPC_WALK jne npc_update_wait invoke NpcMove3D,esi,milliseconds jmp npc_update_sync npc_update_wait: sub npcLife3D[ebx].remaining,eax ja npc_update_sync cmp npcLife3D[ebx].state,NPC_AWAY jne npc_update_choose mov eax,npcLife3D[ebx].identity add eax,6 cmp eax,NPC_IDENTITY_COUNT jb @F sub eax,NPC_IDENTITY_COUNT @@: mov npcLife3D[ebx].identity,eax mov npcLife3D[ebx].state,NPC_STAND npc_update_choose: mov eax,npcLife3D[ebx].routeStep xor edx,edx mov ecx,NPC_DEST_COUNT div ecx mov eax,npcVisitRoute3D[edx*4] mov destination,eax inc npcLife3D[ebx].routeStep invoke NpcPlanPath3D,esi,destination test eax,eax jz npc_update_retry mov npcLife3D[ebx].state,NPC_WALK jmp npc_update_sync npc_update_retry: mov npcLife3D[ebx].state,NPC_STAND mov npcLife3D[ebx].remaining,1000 npc_update_sync: imul edi,esi,SIZEOF NpcMindEntity mov eax,npcLife3D[ebx].state mov casinoNpcs3D[edi].state,eax mov eax,npcLife3D[ebx].remaining mov casinoNpcs3D[edi].stateTimer,eax ; Keep the old public position array coherent for existing world checks. imul ecx,esi,SIZEOF CityProp3D mov eax,casinoNpcs3D[edi].posX mov hallGuests3D[ecx].posX,eax mov eax,casinoNpcs3D[edi].posY mov hallGuests3D[ecx].posY,eax inc esi cmp esi,NPC_LIFE_COUNT jb npc_update_person npc_update_done: ret UpdateNpcLife3D ENDP RenderNpcLife3D PROC uses ebx esi edi cmp npcLifeReady3D,1 jne npc_draw_done xor esi,esi npc_draw_depth: imul ebx,esi,SIZEOF NpcMindEntity invoke ProjectCityPoint3D,casinoNpcs3D[ebx].posX,casinoNpcs3D[ebx].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 imul edi,eax,SIZEOF NpcMindEntity mov eax,casinoNpcs3D[edi].posX mov npcSprite3D.posX,eax mov eax,casinoNpcs3D[edi].posY mov npcSprite3D.posY,eax mov npcSprite3D.worldW,FP_1*3/5 mov npcSprite3D.worldH,FP_1*6/5 mov npcSprite3D.groundZ,-FP_1*6/5*4/224 mov npcSprite3D.asset,OFFSET npcGuestArt3D cmp npcGuestArt3D.pixels,0 je npc_draw_next mov eax,npcLife3D[ebx].identity mov eax,npcGuestAppearance3D[eax*4] imul eax,6 mov npcSprite3D.frame,eax cmp npcLife3D[ebx].state,NPC_SIT jne npc_draw_walk add npcSprite3D.frame,5 ; Seated pose is already shorter within the same foot-anchored canvas. mov npcSprite3D.worldW,FP_1*3/5 mov npcSprite3D.worldH,FP_1*6/5 jmp npc_draw_sprite npc_draw_walk: cmp npcLife3D[ebx].state,NPC_WALK jne npc_draw_sprite cmp npcWalkArt3D.pixels,0 je npc_draw_sprite mov npcSprite3D.asset,OFFSET npcWalkArt3D mov npcSprite3D.groundZ,0 mov eax,npcLife3D[ebx].identity mov eax,npcGuestAppearance3D[eax*4] shl eax,3 mov npcSprite3D.frame,eax mov ebx,npcDrawOrder3D[esi*4] mov eax,npcWalkDistance3D[ebx*4] xor edx,edx mov ecx,FP_1*3/32 div ecx and eax,7 add npcSprite3D.frame,eax ; Match the drawn facing to the actual camera-relative direction of travel. mov eax,npcWalkVelocityY3D[ebx*4] imul playerDirX shrd eax,edx,16 mov edi,eax mov eax,npcWalkVelocityX3D[ebx*4] imul playerDirY shrd eax,edx,16 sub edi,eax jns npc_draw_sprite cmp npcWalkLeftArt3D.pixels,0 je npc_draw_sprite mov npcSprite3D.asset,OFFSET npcWalkLeftArt3D npc_draw_sprite: invoke DrawCityObject3D,OFFSET npcSprite3D,0 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 imul edi,esi,SIZEOF NpcMindEntity mov eax,casinoNpcs3D[edi].posX sub eax,playerX sar eax,8 mov deltaX,eax imul eax,eax mov distance,eax mov eax,casinoNpcs3D[edi].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