Skip to content

Fix the per-step check for a path toward an item or object - #253

Open
ResurrectedTrader wants to merge 1 commit into
ThePhrozenKeep:masterfrom
ResurrectedTrader:fix/path-target-unit-step-check-v2
Open

ResurrectedTrader wants to merge 1 commit into
ThePhrozenKeep:masterfrom
ResurrectedTrader:fix/path-target-unit-step-check-v2

Conversation

@ResurrectedTrader

Copy link
Copy Markdown
Contributor

sub_6FD5DB70 is the per-step check for a path with a target unit. In MOO it only keeps the current path for a player or monster target that has moved at most 5 subtiles. Every other target type (item, object, ...) falls through to D2Common_10236 and re-paths on every step, so walking to an item or an object re-solves the path each step and stalls whenever that re-path fails.

In the game, the "moved more than 5 subtiles" re-path applies only to players and monsters. After that, any target keeps its path while points remain, or while the unit stands on the final point.

1.10f

Inlined in D2Common_10226 (D2Common.0x6FDAC9A0). This is the whole inlined function (esi = pDynamicPath; +0x14 tPrevTargetCoord, +0x18 tFinalTargetCoord, +0x24 / +0x28 point index / count, +0x30 pUnit, +0x3C dwPathType, +0x58 pTargetUnit, +0x93 nStepNum). 6FDACB47 continues the step (TRUE), 6FDACB32 stops it (FALSE). Any target of type > 1 jumps past the distance test straight to the remaining-points test:

6FDACA34  test    dword ptr [esi+34h], 40000h       ; PATH_MISSILE_MASK: not this check
6FDACA3B  jnz     loc_6FDACB47
6FDACA41  mov     eax, [esi+3Ch]                    ; dwPathType
6FDACA44  cmp     eax, 5                            ; PATHTYPE_MON_CIRCLE_CW / CCW ...
6FDACA47  jl      short loc_6FDACA5C
6FDACA49  cmp     eax, 6
6FDACA4C  jg      short loc_6FDACA5C
6FDACA4E  mov     edx, [esi+24h]
6FDACA51  mov     eax, [esi+28h]
6FDACA54  cmp     edx, eax
6FDACA56  jge     loc_6FDACB47                      ; ... at the last point -> TRUE
6FDACA5C  mov     eax, [esi+58h]                    ; pTargetUnit
6FDACA5F  mov     edi, [esi+30h]                    ; pUnit
6FDACA62  cmp     eax, ebx
6FDACA64  jnz     short loc_6FDACA98                ; no target:
6FDACA66  mov     eax, [esi+24h]
6FDACA69  mov     ecx, [esi+28h]
6FDACA6C  cmp     eax, ecx
6FDACA6E  jl      loc_6FDACB47                      ;   points remain -> TRUE
6FDACA74  mov     ax, [esi+2]
6FDACA78  mov     cx, [esi+6]
6FDACA7C  cmp     ax, [esi+18h]
6FDACA80  jnz     short loc_6FDACA8C
6FDACA82  cmp     cx, [esi+1Ah]
6FDACA86  jz      loc_6FDACB47                      ;   on the final point -> TRUE
6FDACA8C  push    ebx
6FDACA8D  push    edi
6FDACA8E  call    D2Common_10236                    ;   D2Common_10236(pUnit, FALSE)
6FDACA93  jmp     loc_6FDACB2E
6FDACA98  push    edi
6FDACA99  push    eax
6FDACA9A  call    D2Common_10399                    ; distance to target
6FDACA9F  xor     ecx, ecx
6FDACAA1  mov     cl, [esi+93h]                     ; nStepNum
6FDACAA7  cmp     eax, ecx
6FDACAA9  jle     loc_6FDACB32                      ; in reach -> FALSE
6FDACAAF  mov     bx, [esi+2]                       ; position X
6FDACAB3  mov     bp, [esi+6]                       ; position Y
6FDACAB7  mov     edx, esi
6FDACAB9  lea     ecx, [esp+20h+var_10]
6FDACABD  call    sub_6FDAB940                      ; a3 = target position
6FDACAC2  mov     eax, [esi+58h]                    ; pTargetUnit, read again
6FDACAC5  test    eax, eax                          ; NULL check (kept)
6FDACAC7  jz      short loc_6FDACB0E                ; NULL -> points test
6FDACAC9  mov     eax, [eax]                        ; dwUnitType
6FDACACB  test    eax, eax
6FDACACD  jl      short loc_6FDACB0E
6FDACACF  cmp     eax, 1                            ; <== player or monster only:
6FDACAD2  jg      short loc_6FDACB0E                ; <== other types skip the moved test
6FDACAD4  mov     edx, [esp+20h+var_10]
6FDACAD8  xor     eax, eax
6FDACADA  mov     ax, [esi+14h]                     ; tPrevTargetCoord.X - a3.X
6FDACADE  and     edx, 0FFFFh
6FDACAE4  sub     eax, edx
6FDACAE6  mov     edx, [esp+20h+var_10+2]
6FDACAEA  xor     ecx, ecx
6FDACAEC  and     edx, 0FFFFh
6FDACAF2  mov     cx, [esi+16h]                     ; tPrevTargetCoord.Y - a3.Y
6FDACAF6  sub     ecx, edx
6FDACAF8  test    eax, eax
6FDACAFA  jge     short loc_6FDACAFE
6FDACAFC  neg     eax
6FDACAFE  cmp     eax, 5
6FDACB01  jg      short loc_6FDACB24                ; |dx| > 5 -> re-path
6FDACB03  test    ecx, ecx
6FDACB05  jge     short loc_6FDACB09
6FDACB07  neg     ecx
6FDACB09  cmp     ecx, 5
6FDACB0C  jg      short loc_6FDACB24                ; |dy| > 5 -> re-path
6FDACB0E  mov     eax, [esi+24h]                    ; <== every target type:
6FDACB11  mov     ecx, [esi+28h]
6FDACB14  cmp     eax, ecx                          ; <== dwCurrentPointIdx < dwPathPoints
6FDACB16  jl      short loc_6FDACB45                ; <== -> TRUE
6FDACB18  cmp     [esi+18h], bx                     ; <== tFinalTargetCoord == position
6FDACB1C  jnz     short loc_6FDACB24
6FDACB1E  cmp     [esi+1Ah], bp
6FDACB22  jz      short loc_6FDACB45                ; <== -> TRUE
6FDACB24  push    1
6FDACB26  push    edi
6FDACB27  call    D2Common_10236                    ; D2Common_10236(pUnit, TRUE)
6FDACB2C  xor     ebx, ebx
6FDACB2E  cmp     eax, ebx
6FDACB30  jnz     short loc_6FDACB47                ; re-path succeeded -> TRUE
6FDACB32  mov     ecx, esi                          ; FALSE
6FDACB34  call    sub_6FDAD330
6FDACB39  pop     edi
6FDACB3A  pop     esi
6FDACB3B  pop     ebp
6FDACB3C  xor     eax, eax
6FDACB3E  pop     ebx
6FDACB3F  add     esp, 10h
6FDACB42  retn    8
6FDACB45  xor     ebx, ebx
6FDACB47  mov     ecx, [esi+24h]                    ; TRUE
6FDACB4A  mov     eax, [esi+28h]
6FDACB4D  cmp     ecx, eax

1.14d

PATH_CheckIfTargetReached (Game.exe 0x6503F0) is the same function, not inlined:

006503F0  push    ebp
006503F1  mov     ebp, esp
006503F3  mov     eax, [esi+3Ch]                    ; dwPathType
006503F6  add     eax, 0FFFFFFFBh
006503F9  sub     esp, 8
006503FC  cmp     eax, 1                            ; PATHTYPE_MON_CIRCLE_CW / CCW ...
006503FF  ja      short loc_650412
00650401  mov     eax, [esi+24h]
00650404  cmp     eax, [esi+28h]
00650407  jl      short loc_650412
00650409  mov     eax, 1                            ; ... at the last point -> TRUE
0065040E  mov     esp, ebp
00650410  pop     ebp
00650411  retn
00650412  mov     eax, [esi+58h]                    ; pTargetUnit
00650415  test    eax, eax
00650417  push    ebx
00650418  mov     ebx, [esi+30h]
0065041B  jnz     short loc_650450                  ; no target:
0065041D  mov     ecx, [esi+24h]
00650420  cmp     ecx, [esi+28h]
00650423  jl      short loc_650446
00650425  mov     ax, [esi+2]
00650429  cmp     ax, [esi+18h]
0065042D  mov     cx, [esi+6]
00650431  jnz     short loc_650439
00650433  cmp     cx, [esi+1Ah]
00650437  jz      short loc_650446
00650439  push    0
0065043B  push    ebx
0065043C  call    PATH_RecalculatePath              ;   D2Common_10236(pUnit, FALSE)
00650441  pop     ebx
00650442  mov     esp, ebp
00650444  pop     ebp
00650445  retn
00650446  mov     eax, 1
0065044B  pop     ebx
0065044C  mov     esp, ebp
0065044E  pop     ebp
0065044F  retn
00650450  push    ebx
00650451  push    eax
00650452  call    GetDistanceBetweenUnits           ; D2Common_10399
00650457  movzx   edx, byte ptr [esi+93h]
0065045E  cmp     eax, edx
00650460  jg      short loc_650469                  ; out of reach
00650462  xor     eax, eax                          ; in reach -> FALSE
00650464  pop     ebx
00650465  mov     esp, ebp
00650467  pop     ebp
00650468  retn
00650469  mov     ax, [esi+6]
0065046D  push    edi
0065046E  mov     di, [esi+2]
00650472  mov     edx, esi
00650474  lea     ecx, [ebp+var_4]
00650477  mov     [ebp+var_6], ax
0065047B  call    DRLGPATH_GetMissileTargetPosition ; sub_6FDAB940
00650480  mov     eax, [esi+58h]                    ; pTargetUnit, read again
00650483  test    eax, eax                          ; NULL check (kept)
00650485  jz      short loc_6504BC                  ; NULL -> points test
00650487  mov     eax, [eax]
00650489  test    eax, eax
0065048B  jl      short loc_6504BC
0065048D  cmp     eax, 1                            ; <== player or monster only:
00650490  jg      short loc_6504BC                  ; <== other types skip the moved test
00650492  movzx   ecx, [ebp+var_4]
00650496  movzx   eax, word ptr [esi+14h]
0065049A  movzx   edx, [ebp+var_2]
0065049E  sub     eax, ecx
006504A0  movzx   ecx, word ptr [esi+16h]
006504A4  sub     ecx, edx
006504A6  test    eax, eax
006504A8  jge     short loc_6504AC
006504AA  neg     eax
006504AC  cmp     eax, 5
006504AF  jg      short loc_6504D4                  ; |dx| > 5 -> re-path
006504B1  test    ecx, ecx
006504B3  jge     short loc_6504B7
006504B5  neg     ecx
006504B7  cmp     ecx, 5
006504BA  jg      short loc_6504D4                  ; |dy| > 5 -> re-path
006504BC  mov     eax, [esi+24h]                    ; <== every target type:
006504BF  cmp     eax, [esi+28h]
006504C2  jl      short loc_6504E2                  ; <== points remain -> TRUE
006504C4  cmp     [esi+18h], di
006504C8  jnz     short loc_6504D4
006504CA  mov     cx, [ebp+var_6]
006504CE  cmp     [esi+1Ah], cx
006504D2  jz      short loc_6504E2                  ; <== on the final point -> TRUE
006504D4  push    1
006504D6  push    ebx
006504D7  call    PATH_RecalculatePath              ; D2Common_10236(pUnit, TRUE)
006504DC  pop     edi
006504DD  pop     ebx
006504DE  mov     esp, ebp
006504E0  pop     ebp
006504E1  retn
006504E2  pop     edi
006504E3  mov     eax, 1
006504E8  pop     ebx
006504E9  mov     esp, ebp
006504EB  pop     ebp
006504EC  retn

Change

Most of the diff is re-nesting. For a player or monster target the result is unchanged: re-path if it moved more than 5 subtiles, otherwise keep the path while points remain or the unit stands on the final point. The behaviour change is for item, object and other targets, which now take the points test too instead of re-pathing every step.

The game reads pTargetUnit again after sub_6FDAB940 and NULL-checks it (0x6FDACAC5 / 0x00650483), with NULL going to the points test. The check stays, folded into the type test: if (pTargetUnit && (type == UNIT_PLAYER || type == UNIT_MONSTER)). Not version-gated, since 1.10f and 1.14d agree.

This replaces #246, which dropped that NULL check.

🤖 Generated with Claude Code

sub_6FD5DB70 (the per-step check for a path with a target unit) kept
the current path only for a player or monster target that moved at
most 5 subtiles. Any other target type fell through to D2Common_10236
and re-pathed on every step.

The game applies the "moved more than 5 subtiles" re-path to players
and monsters only, and then keeps any target's path while points remain
or while the unit stands on the final point. 1.10f, inlined in
D2Common_10226 (D2Common.0x6FDACAC2-0x6FDACB22): a target of type > 1
jumps past the distance test straight to the remaining-points test.
1.14d is the same (Game.exe.0x650480-0x6504D2).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant