KAZIK/src_graphics/native_durak.inc
Artvasil09 881a0061dc Add native NPC visitors with contracts, Clover slots, Pyramid, and party table scenes
Implement 32x32 BFS visitor navigation and daily contracts system with companion .npc journals; add native assembly scenes for Clover slots, Pyramid cards, Blackjack Party and Russian Party; package animation and sprite packs; add comprehensive offline test suites and rebuild shipping binaries.
2026-10-13 09:47:33 +03:00

876 lines
18 KiB
PHP

InitNativeDurak3D PROC
call LoadCardTableAssets3D
mov cardExitActive3D, 0
mov dkState3D, DK_IDLE
mov dkStake3D, 0
mov dkError3D, 0
mov durakPlayerCnt, 0
mov durakAICnt, 0
mov durakDeckLeft, 0
mov dkPendingCard3D, 0FFFFFFFFh
call ClearDurakTable
call ClearKeys3D
ret
InitNativeDurak3D ENDP
; Removing ranks 2..5 from a uniformly shuffled deck preserves uniformity.
; The exposed trump remains in the talon and is dealt last.
StartNativeDurak3D PROC uses ebx esi edi
cmp dkState3D, DK_IDLE
je @F
cmp dkState3D, DK_RESULT
jne dk_start_done
@@:
mov eax, dkBet3D
cmp bankroll, eax
jb dk_start_no_cash
sub bankroll, eax
mov dkStake3D, eax
invoke ShuffleCards3D, OFFSET dkDeck3D
xor esi, esi
xor edi, edi
dk_compact:
mov ebx, dkDeck3D[esi*4]
invoke DurakCardRank, ebx
cmp eax, 4
jb @F
mov dkDeck3D[edi*4], ebx
inc edi
@@:
inc esi
cmp esi, 52
jb dk_compact
mov eax, dkDeck3D[35*4]
mov durakTrumpCard, eax
invoke DurakCardSuit, eax
mov durakTrumpSuit, eax
mov durakPlayerCnt, 0
mov durakAICnt, 0
mov dkCursor3D, 0
mov durakDeckLeft, 36
mov dkDiscardCount3D, 0
mov dkBouts3D, 0
mov dkError3D, 0
mov dkSelection3D, 0
mov dkPage3D, 0
mov dkTaking3D, 0
mov dkPendingCard3D, 0FFFFFFFFh
call ClearDurakTable
xor esi, esi
dk_deal_hands:
mov eax, dkDeck3D[esi*8]
invoke AddDurakCard, OFFSET durakPlayerHand, OFFSET durakPlayerCnt, eax
mov eax, dkDeck3D[esi*8+4]
invoke AddDurakCard, OFFSET durakAIHand, OFFSET durakAICnt, eax
inc esi
cmp esi, 6
jb dk_deal_hands
mov dkCursor3D, 12
mov durakDeckLeft, 24
; First attacker: holder of the lowest trump, random if neither has one.
mov edi, 0FFFFFFFFh
xor ebx, ebx
xor esi, esi
dk_low_trump:
mov eax, durakPlayerHand[esi*4]
cmp ebx, 0
je @F
mov eax, durakAIHand[esi*4]
@@:
push eax
invoke DurakCardSuit, eax
pop edx
cmp eax, durakTrumpSuit
jne dk_low_next
cmp edx, edi
jae dk_low_next
mov edi, edx
mov dkAttacker3D, ebx
dk_low_next:
inc esi
cmp esi, 6
jb dk_low_trump
xor esi, esi
inc ebx
cmp ebx, 2
jb dk_low_trump
cmp edi, 0FFFFFFFFh
jne @F
invoke RandomRange3D, 2
mov dkAttacker3D, eax
@@:
mov eax, dkAttacker3D
mov dkActor3D, eax
mov dkTimer3D, 0
mov dkState3D, DK_DEAL
call ClearKeys3D
invoke PlayCasinoSound3D, OFFSET soundCards, 0, 1
call SaveProgress3D
dk_start_done:
ret
dk_start_no_cash:
mov dkError3D, 1
ret
StartNativeDurak3D ENDP
NativeDurakSettle3D PROC result:DWORD
cmp dkState3D, DK_RESULT
je dk_settle_done
mov dkState3D, DK_RESULT
mov eax, result
mov dkOutcome3D, eax
cmp eax, 1
je dk_settle_loss
mov eax, dkStake3D
cmp result, 2
je @F
shl eax, 1
@@:
add bankroll, eax
invoke PlayCasinoSound3D, OFFSET soundRoundWin, 0, 1
jmp dk_settle_save
dk_settle_loss:
invoke PlayCasinoSound3D, OFFSET soundRoundLose, 0, 1
dk_settle_save:
call ClearKeys3D
xor eax,eax
cmp dkOutcome3D,0
sete al
invoke NpcRecordRound3D,5,eax
call SaveProgress3D
dk_settle_done:
ret
NativeDurakSettle3D ENDP
DurakClampSelection3D PROC
mov eax, durakPlayerCnt
test eax, eax
jz dk_select_zero
cmp dkSelection3D, eax
jb dk_select_page
dec eax
mov dkSelection3D, eax
dk_select_page:
mov eax, dkSelection3D
shr eax, 3
shl eax, 3
mov dkPage3D, eax
ret
dk_select_zero:
mov dkSelection3D, 0
mov dkPage3D, 0
ret
DurakClampSelection3D ENDP
DurakMoveSelection3D PROC amount:SDWORD
cmp durakPlayerCnt, 0
je dk_move_done
mov eax, dkSelection3D
add eax, amount
jns @F
mov eax, durakPlayerCnt
dec eax
@@:
cmp eax, durakPlayerCnt
jb @F
xor eax, eax
@@:
mov dkSelection3D, eax
call DurakClampSelection3D
dk_move_done:
ret
DurakMoveSelection3D ENDP
DurakBeginBout3D PROC
call ClearDurakTable
mov eax, dkAttacker3D
mov dkOriginal3D, eax
mov dkActor3D, eax
xor eax, 1
mov ecx, durakPlayerCnt
test eax, eax
jz @F
mov ecx, durakAICnt
@@:
cmp ecx, 6
jbe @F
mov ecx, 6
@@:
mov dkLimit3D, ecx
mov dkTaking3D, 0
mov dkState3D, DK_TURN
mov dkTimer3D, 0
mov dkError3D, 0
inc dkBouts3D
call DurakClampSelection3D
call ClearKeys3D
ret
DurakBeginBout3D ENDP
DurakCanTransfer3D PROC uses ebx actor:DWORD,card:DWORD
cmp dkVariant3D, 0
je dk_transfer_no
cmp dkTaking3D, 0
jne dk_transfer_no
mov eax, actor
cmp eax, dkAttacker3D
je dk_transfer_no
cmp durakTableCnt, 0
je dk_transfer_no
cmp durakTableCnt, 6
jae dk_transfer_no
call DurakTableHasDefense
test eax, eax
jnz dk_transfer_no
invoke DurakCardRank, card
mov ebx, eax
invoke DurakCardRank, durakAttackCards[0]
cmp eax, ebx
jne dk_transfer_no
mov eax, durakPlayerCnt
cmp actor, 1
je @F
mov eax, durakAICnt
@@:
cmp eax, durakTableCnt
jbe dk_transfer_no
mov eax, 1
ret
dk_transfer_no:
xor eax, eax
ret
DurakCanTransfer3D ENDP
DurakCanPlay3D PROC uses ebx esi actor:DWORD,index:DWORD,kind:DWORD
cmp dkState3D, DK_TURN
jne dk_can_no
mov eax, actor
cmp eax, dkActor3D
jne dk_can_no
mov esi, OFFSET durakPlayerHand
mov ecx, durakPlayerCnt
test eax, eax
jz @F
mov esi, OFFSET durakAIHand
mov ecx, durakAICnt
@@:
mov eax, index
cmp eax, ecx
jae dk_can_no
mov ebx, [esi+eax*4]
cmp kind, DK_TRANSFER
jne @F
invoke DurakCanTransfer3D, actor, ebx
ret
@@:
cmp kind, DK_DEFEND
jne dk_can_attack
cmp dkTaking3D, 0
jne dk_can_no
mov eax, actor
cmp eax, dkAttacker3D
je dk_can_no
call DurakFirstUnbeaten
cmp eax, 0FFFFFFFFh
je dk_can_no
invoke CanDurakBeat, durakAttackCards[eax*4], ebx
ret
dk_can_attack:
cmp kind, DK_ATTACK
jne dk_can_no
mov eax, actor
cmp eax, dkAttacker3D
jne dk_can_no
mov eax, durakTableCnt
cmp eax, dkLimit3D
jae dk_can_no
test eax, eax
jz dk_can_yes
invoke DurakRankOnTable, ebx
ret
dk_can_yes:
mov eax, 1
ret
dk_can_no:
xor eax, eax
ret
DurakCanPlay3D ENDP
; A card has one owner even while moving: removed from the hand, committed
; to its table slot only when this nonblocking flight finishes.
DurakPlay3D PROC uses ebx esi actor:DWORD,index:DWORD,kind:DWORD
invoke DurakCanPlay3D, actor, index, kind
test eax, eax
jz dk_play_invalid
mov eax, actor
mov dkPendingActor3D, eax
mov eax, kind
mov dkPendingKind3D, eax
mov eax, durakTableCnt
cmp kind, DK_DEFEND
jne @F
call DurakFirstUnbeaten
@@:
mov dkPendingSlot3D, eax
imul eax, 40
add eax, 12
mov dkTargetY3D, 98
cmp kind, DK_DEFEND
jne @F
add eax, 6
mov dkTargetY3D, 106
@@:
mov dkTargetX3D, eax
mov eax, index
and eax, 7
imul eax, 35
add eax, 20
mov dkPendingX3D, eax
mov dkPendingY3D, 168
cmp actor, 0
je dk_play_player
mov dkPendingX3D, 144
mov dkPendingY3D, 48
invoke RemoveDurakCard, OFFSET durakAIHand, OFFSET durakAICnt, index
jmp dk_play_removed
dk_play_player:
invoke RemoveDurakCard, OFFSET durakPlayerHand, OFFSET durakPlayerCnt, index
dk_play_removed:
mov dkPendingCard3D, eax
mov dkState3D, DK_FLIGHT
mov dkTimer3D, 0
mov dkError3D, 0
call DurakClampSelection3D
call ClearKeys3D
invoke PlayCasinoSound3D, OFFSET soundCards, 0, 1
mov eax, 1
ret
dk_play_invalid:
mov dkError3D, 2
xor eax, eax
ret
DurakPlay3D ENDP
DurakCommitFlight3D PROC uses ebx
cmp dkState3D, DK_FLIGHT
jne dk_commit_done
mov eax, dkPendingCard3D
cmp dkPendingKind3D, DK_DEFEND
jne dk_commit_attack
mov ebx, dkPendingSlot3D
mov durakDefenseCards[ebx*4], eax
jmp dk_commit_turn
dk_commit_attack:
call AddDurakAttack
cmp dkPendingKind3D, DK_TRANSFER
jne dk_commit_turn
mov eax, dkPendingActor3D
mov dkAttacker3D, eax
mov ecx, durakPlayerCnt
test eax, eax
jnz @F
mov ecx, durakAICnt
@@:
cmp ecx, 6
jbe @F
mov ecx, 6
@@:
mov dkLimit3D, ecx
dk_commit_turn:
mov dkPendingCard3D, 0FFFFFFFFh
mov eax, dkAttacker3D
cmp dkTaking3D, 0
jne dk_commit_actor
push eax
call DurakAllDefended
test eax, eax
pop eax
jnz dk_commit_actor
xor eax, 1
dk_commit_actor:
mov dkActor3D, eax
mov dkState3D, DK_TURN
mov dkTimer3D, 0
call ClearKeys3D
dk_commit_done:
ret
DurakCommitFlight3D ENDP
DurakTake3D PROC actor:DWORD
cmp dkState3D, DK_TURN
jne dk_take_done
mov eax, actor
cmp eax, dkActor3D
jne dk_take_done
cmp eax, dkAttacker3D
je dk_take_done
cmp dkTaking3D, 0
jne dk_take_done
cmp durakTableCnt, 0
je dk_take_done
mov dkTaking3D, 1
mov eax, dkAttacker3D
mov dkActor3D, eax
mov dkTimer3D, 0
mov dkError3D, 0
call ClearKeys3D
dk_take_done:
ret
DurakTake3D ENDP
DurakFinish3D PROC actor:DWORD
cmp dkState3D, DK_TURN
jne dk_finish_done
mov eax, actor
cmp eax, dkActor3D
jne dk_finish_done
cmp eax, dkAttacker3D
jne dk_finish_done
cmp durakTableCnt, 0
je dk_finish_done
cmp dkTaking3D, 0
jne dk_finish_sweep
call DurakAllDefended
test eax, eax
jz dk_finish_done
dk_finish_sweep:
mov dkState3D, DK_SWEEP
mov dkTimer3D, 0
mov dkError3D, 0
call ClearKeys3D
dk_finish_done:
ret
DurakFinish3D ENDP
DurakRefill3D PROC uses ebx esi actor:DWORD
mov ebx, OFFSET durakPlayerCnt
mov esi, OFFSET durakPlayerHand
cmp actor, 0
je @F
mov ebx, OFFSET durakAICnt
mov esi, OFFSET durakAIHand
@@:
dk_refill_loop:
cmp DWORD PTR [ebx], 6
jae dk_refill_done
cmp dkCursor3D, 36
jae dk_refill_done
mov eax, dkCursor3D
mov eax, dkDeck3D[eax*4]
invoke AddDurakCard, esi, ebx, eax
inc dkCursor3D
dec durakDeckLeft
jmp dk_refill_loop
dk_refill_done:
ret
DurakRefill3D ENDP
DurakResolveBout3D PROC uses ebx esi
cmp dkState3D, DK_SWEEP
jne dk_resolve_done
cmp dkTaking3D, 0
je dk_discard
cmp dkAttacker3D, 0
je dk_collect_ai
invoke CollectDurakTable, OFFSET durakPlayerHand, OFFSET durakPlayerCnt
jmp dk_bout_refill
dk_collect_ai:
invoke CollectDurakTable, OFFSET durakAIHand, OFFSET durakAICnt
jmp dk_bout_refill
dk_discard:
xor esi, esi
mov ebx, dkDiscardCount3D
dk_discard_pair:
cmp esi, durakTableCnt
jae dk_discard_done
mov eax, durakAttackCards[esi*4]
mov dkDiscard3D[ebx*4], eax
inc ebx
mov eax, durakDefenseCards[esi*4]
mov dkDiscard3D[ebx*4], eax
inc ebx
inc esi
jmp dk_discard_pair
dk_discard_done:
mov dkDiscardCount3D, ebx
call ClearDurakTable
dk_bout_refill:
invoke DurakRefill3D, dkOriginal3D
mov eax, dkOriginal3D
xor eax, 1
invoke DurakRefill3D, eax
call CheckDurakEnd
test eax, eax
jz dk_continue_bout
dec eax ; legacy 1=win, 2=loss, 3=draw
invoke NativeDurakSettle3D, eax
ret
dk_continue_bout:
; A defensive turn that takes the pile is skipped.
cmp dkTaking3D, 0
jne @F
xor dkAttacker3D, 1
@@:
; A pathological repeating take cycle is a push, not an arbitrary loss.
cmp dkBouts3D, 300
jb @F
invoke NativeDurakSettle3D, 2
ret
@@:
call DurakBeginBout3D
dk_resolve_done:
ret
DurakResolveBout3D ENDP
DurakCardValue3D PROC uses ebx card:DWORD
invoke DurakCardRank, card
mov ebx, eax
invoke DurakCardSuit, card
cmp eax, durakTrumpSuit
jne @F
add ebx, 32
@@:
mov eax, ebx
ret
DurakCardValue3D ENDP
; AI sees only its own cards, public table/trump and the defender's count.
; This helper also serves the deterministic regression player's legal moves.
DurakFindMove3D PROC uses ebx esi edi actor:DWORD,kind:DWORD
local bestValue:DWORD,bestIndex:DWORD,handPtr:DWORD,handCount:DWORD
mov bestValue, 0FFFFFFFFh
mov bestIndex, 0FFFFFFFFh
mov handPtr, OFFSET durakPlayerHand
mov eax, durakPlayerCnt
cmp actor, 0
je @F
mov handPtr, OFFSET durakAIHand
mov eax, durakAICnt
@@:
mov handCount, eax
xor esi, esi
dk_find_card:
cmp esi, handCount
jae dk_find_done
invoke DurakCanPlay3D, actor, esi, kind
test eax, eax
jz dk_find_next
mov ebx, handPtr
invoke DurakCardValue3D, DWORD PTR [ebx+esi*4]
cmp eax, bestValue
jae dk_find_next
mov bestValue, eax
mov bestIndex, esi
dk_find_next:
inc esi
jmp dk_find_card
dk_find_done:
mov eax, bestIndex
ret
DurakFindMove3D ENDP
DurakAITurn3D PROC
cmp dkAttacker3D, 1
je dk_ai_attack
invoke DurakFindMove3D, 1, DK_TRANSFER
cmp eax, 0FFFFFFFFh
je @F
invoke DurakPlay3D, 1, eax, DK_TRANSFER
ret
@@:
invoke DurakFindMove3D, 1, DK_DEFEND
cmp eax, 0FFFFFFFFh
je dk_ai_take
invoke DurakPlay3D, 1, eax, DK_DEFEND
ret
dk_ai_take:
invoke DurakTake3D, 1
ret
dk_ai_attack:
invoke DurakFindMove3D, 1, DK_ATTACK
cmp eax, 0FFFFFFFFh
je dk_ai_finish
invoke DurakPlay3D, 1, eax, DK_ATTACK
ret
dk_ai_finish:
invoke DurakFinish3D, 1
ret
DurakAITurn3D ENDP
DurakMouse3D PROC uses ebx
cmp mouseClick3D, 0
je dk_mouse_done
mov mouseClick3D, 0
cmp mouseY3D, 3
jb dk_mouse_done
cmp mouseY3D, 17
jae @F
cmp mouseX3D, 252
jb dk_mouse_done
cmp mouseX3D, 312
jae dk_mouse_done
call RequestCardExit3D
ret
@@:
cmp dkState3D, DK_IDLE
je dk_mouse_ready
cmp dkState3D, DK_RESULT
je dk_mouse_ready
cmp dkState3D, DK_TURN
jne dk_mouse_done
cmp dkActor3D, 0
jne dk_mouse_done
cmp mouseY3D, 168
jb dk_mouse_actions
cmp mouseY3D, 216
jae dk_mouse_actions
cmp mouseX3D, 16
jb dk_mouse_prev
cmp mouseX3D, 304
jae dk_mouse_next
mov eax, mouseX3D
sub eax, 20
js dk_mouse_done
xor edx, edx
mov ecx, 35
div ecx
cmp edx, 34
jae dk_mouse_done
cmp eax, 8
jae dk_mouse_done
add eax, dkPage3D
cmp eax, durakPlayerCnt
jae dk_mouse_done
mov dkSelection3D, eax
ret
dk_mouse_prev:
cmp durakPlayerCnt, DK_PAGE_SIZE
jbe dk_mouse_done
cmp mouseX3D, 2
jb dk_mouse_done
cmp mouseY3D, 180
jb dk_mouse_done
cmp mouseY3D, 200
jae dk_mouse_done
mov pressedKeys3D[21h], 1
ret
dk_mouse_next:
cmp durakPlayerCnt, DK_PAGE_SIZE
jbe dk_mouse_done
cmp mouseX3D, 318
jae dk_mouse_done
cmp mouseY3D, 180
jb dk_mouse_done
cmp mouseY3D, 200
jae dk_mouse_done
mov pressedKeys3D[22h], 1
ret
dk_mouse_actions:
cmp mouseY3D, 222
jb dk_mouse_done
cmp mouseY3D, 238
jae dk_mouse_done
mov eax, mouseX3D
mov ecx, 0Dh
cmp eax, 8
jb dk_mouse_done
cmp eax, 78
jb dk_mouse_key
mov ecx, 42h
cmp eax, 86
jb dk_mouse_done
cmp eax, 156
jb dk_mouse_key
mov ecx, 54h
cmp eax, 164
jb dk_mouse_done
cmp eax, 234
jb dk_mouse_key
mov ecx, 44h
cmp eax, 242
jb dk_mouse_done
cmp eax, 312
jb dk_mouse_key
ret
dk_mouse_ready:
cmp mouseY3D, 222
jb dk_mouse_bet
cmp mouseY3D, 238
jae dk_mouse_done
cmp mouseX3D, 8
jb dk_mouse_done
cmp mouseX3D, 118
jb dk_mouse_variant
cmp mouseX3D, 125
jb dk_mouse_done
cmp mouseX3D, 195
jae dk_mouse_done
mov ecx, 20h
jmp dk_mouse_key
dk_mouse_variant:
mov ecx, 54h
jmp dk_mouse_key
dk_mouse_bet:
cmp mouseY3D, 199
jb dk_mouse_done
cmp mouseY3D, 215
jae dk_mouse_done
cmp mouseX3D, 11
jb dk_mouse_done
mov ecx, 28h
cmp mouseX3D, 29
jb dk_mouse_key
cmp mouseX3D, 35
jb dk_mouse_done
cmp mouseX3D, 53
jae dk_mouse_done
mov ecx, 26h
dk_mouse_key:
mov pressedKeys3D[ecx], 1
dk_mouse_done:
ret
DurakMouse3D ENDP
HandleNativeDurak3D PROC uses ebx
call HandleCardExit3D
test eax, eax
jnz dk_input_done
call DurakMouse3D
cmp cardExitActive3D, 0
jne dk_input_done
cmp engineMode, MODE_DURAK
jne dk_input_done
cmp dkState3D, DK_IDLE
je dk_ready_input
cmp dkState3D, DK_RESULT
je dk_ready_input
mov eax, deltaTime3D
add dkTimer3D, eax
cmp dkState3D, DK_DEAL
jne @F
cmp dkTimer3D, TABLE_FLIGHT_MS*12
jb dk_input_done
call DurakBeginBout3D
ret
@@:
cmp dkState3D, DK_FLIGHT
jne @F
cmp dkTimer3D, TABLE_FLIGHT_MS
jb dk_input_done
call DurakCommitFlight3D
ret
@@:
cmp dkState3D, DK_SWEEP
jne @F
cmp dkTimer3D, 600
jb dk_input_done
call DurakResolveBout3D
ret
@@:
cmp dkState3D, DK_TURN
jne dk_input_done
cmp dkActor3D, 0
je dk_player_input
cmp dkTimer3D, 650
jb dk_input_done
call DurakAITurn3D
ret
dk_player_input:
invoke Pressed3D, 25h
test eax, eax
jz @F
invoke DurakMoveSelection3D, -1
@@:
invoke Pressed3D, 27h
test eax, eax
jz @F
invoke DurakMoveSelection3D, 1
@@:
invoke Pressed3D, 21h
test eax, eax
jz @F
invoke DurakMoveSelection3D, -8
@@:
invoke Pressed3D, 22h
test eax, eax
jz @F
invoke DurakMoveSelection3D, 8
@@:
mov ebx, 31h
dk_number_select:
invoke Pressed3D, ebx
test eax, eax
jz @F
mov eax, ebx
sub eax, 31h
add eax, dkPage3D
cmp eax, durakPlayerCnt
jae @F
mov dkSelection3D, eax
@@:
inc ebx
cmp ebx, 39h
jb dk_number_select
invoke Pressed3D, 42h
test eax, eax
jz @F
invoke DurakTake3D, 0
ret
@@:
invoke Pressed3D, 44h
test eax, eax
jz @F
invoke DurakFinish3D, 0
ret
@@:
invoke Pressed3D, 54h
test eax, eax
jz @F
invoke DurakPlay3D, 0, dkSelection3D, DK_TRANSFER
ret
@@:
invoke Pressed3D, 0Dh
test eax, eax
jnz dk_player_play
invoke Pressed3D, 20h
test eax, eax
jz dk_input_done
dk_player_play:
mov eax, DK_ATTACK
cmp dkAttacker3D, 0
je @F
mov eax, DK_DEFEND
@@:
invoke DurakPlay3D, 0, dkSelection3D, eax
ret
dk_ready_input:
invoke Pressed3D, 54h
test eax, eax
jz @F
xor dkVariant3D, 1
@@:
invoke Pressed3D, 26h
test eax, eax
jz @F
cmp dkBet3D, 10000
jae @F
add dkBet3D, 100
@@:
invoke Pressed3D, 28h
test eax, eax
jz @F
cmp dkBet3D, 100
jbe @F
sub dkBet3D, 100
@@:
invoke Pressed3D, 20h
test eax, eax
jnz dk_ready_start
invoke Pressed3D, 0Dh
test eax, eax
jz dk_input_done
dk_ready_start:
call StartNativeDurak3D
dk_input_done:
ret
HandleNativeDurak3D ENDP