- Bowling feel: impulse contact solver, string pinsetter cycle, in-world lengthwise return rack and visible ball return lift - Ball weights: 4/5/6/7 kg selection with equal launch work physics, momentum balance, and projected picking - Audio: dedicated MCI channels with 7 synthesized waveaudio cues (release, rolling, impacts, applause, pinsetter, return) - First-person animations: 33-frame player underarm throw/follow-through sequence (player-underarm-v2) - Visitors: 10 persistent shared cast identities with exclusive reservations, activity transitions, and chair sit/rise animations - Tests & verification: comprehensive native regression test suites and updated documentation - Synchronize binaries and update launcher hash to 536E23BED4055562BC63871826FF44FFED037923A9BE8E0F591131DB4FBD104B
684 lines
16 KiB
PHP
684 lines
16 KiB
PHP
; 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
|