- Secret Fortune Teller Room: arcade lantern toggle, sliding timber panel, dedicated staircase and compact cellar chamber - Lived-in den pass v3: cloth hangings, draped workbench, props/clutter, centered entrance, and custom light sources - Bar camera stabilization: remove alcohol horizon sway while retaining drink animations - Synchronize binaries and update launcher hash to EADBA9C8C9BD5B7E3CC4CE486ED647C1CE9383D1DCB6EBC94FE755EA1F4BF927
640 lines
15 KiB
PHP
640 lines
15 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 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
|