From ec8437cd0ba9cdb95aec206fd56fe209ab515ecb Mon Sep 17 00:00:00 2001 From: rtw1x1 Date: Tue, 30 Dec 2025 19:36:05 +0000 Subject: [PATCH] fix: Uninitialized move duration causing NaN/INT_MIN position sync --- src/game/char.cpp | 55 +++++++++++++++++++++++++++++++++++------ src/game/char_state.cpp | 28 ++++++++++++++++++++- src/game/input_main.cpp | 2 +- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/game/char.cpp b/src/game/char.cpp index c887c42..16546ff 100644 --- a/src/game/char.cpp +++ b/src/game/char.cpp @@ -2733,8 +2733,8 @@ void CHARACTER::Stop() bool CHARACTER::Goto(long x, long y) { - // TODO 거리체크 필요 - // 같은 위치면 이동할 필요 없음 (자동 성공) + // TODO: Distance check required + // If the location is the same, no need to move (automatic success) if (GetX() == x && GetY() == y) return false; @@ -2742,6 +2742,14 @@ bool CHARACTER::Goto(long x, long y) { if (!IsState(m_stateMove)) { + // Without this, StateMove() may divide by 0 (duration from ctor/init) and produce NaN/INT_MIN coords. + CalculateMoveDuration(); + + // If duration is 0, do NOT enter move state (it would snap+stop in StateMove). + // This indicates invalid speed or no actual movement. + if (m_dwMoveDuration == 0) + return false; + m_dwStateDuration = 4; GotoState(m_stateMove); } @@ -2753,12 +2761,15 @@ bool CHARACTER::Goto(long x, long y) CalculateMoveDuration(); + // If duration is 0, do NOT enter move state. + if (m_dwMoveDuration == 0) + return false; + m_dwStateDuration = 4; - if (!IsState(m_stateMove)) { - MonsterLog("[MOVE] %s", GetVictim() ? "대상추적" : "그냥이동"); + MonsterLog("[MOVE] %s", GetVictim() ? "To victim" : "Free"); if (GetVictim()) { @@ -2772,7 +2783,6 @@ bool CHARACTER::Goto(long x, long y) return true; } - DWORD CHARACTER::GetMotionMode() const { DWORD dwMode = MOTION_MODE_GENERAL; @@ -2850,11 +2860,40 @@ void CHARACTER::CalculateMoveDuration() m_posStart.y = GetY(); float fDist = DISTANCE_SQRT(m_posStart.x - m_posDest.x, m_posStart.y - m_posDest.y); - float motionSpeed = GetMoveMotionSpeed(); - m_dwMoveDuration = CalculateDuration(GetLimitPoint(POINT_MOV_SPEED), - (int) ((fDist / motionSpeed) * 1000.0f)); + // If there is no real movement or speed is invalid, duration becomes 0. + // This should NOT lead to NaN in StateMove (StateMove will guard). + if (motionSpeed <= 0.0f || fDist <= 0.0f) + { + sys_err("MOVE_DURATION_INVALID: name=%s vid=%u map=%ld dist=%.3f speed=%.3f start=(%d,%d) dest=(%d,%d)", + GetName(), (DWORD)GetVID(), GetMapIndex(), fDist, motionSpeed, (int)m_posStart.x, (int)m_posStart.y, (int)m_posDest.x, (int)m_posDest.y); + + m_dwMoveDuration = 0; + m_dwMoveStartTime = get_dword_time(); + return; + } + + // Base duration in ms. int truncation can produce 0 for tiny moves. + int baseMs = (int)((fDist / motionSpeed) * 1000.0f); + if (baseMs <= 0) + { + sys_err("MOVE_DURATION_BASE_ZERO: name=%s vid=%u map=%ld dist=%.3f speed=%.3f base=%d start=(%d,%d) dest=(%d,%d)", + GetName(), (DWORD)GetVID(), GetMapIndex(), fDist, motionSpeed, baseMs, (int)m_posStart.x, (int)m_posStart.y, (int)m_posDest.x, (int)m_posDest.y); + + baseMs = 1; + } + + m_dwMoveDuration = CalculateDuration(GetLimitPoint(POINT_MOV_SPEED), baseMs); + + // Defensive clamp: some duration formulas can still return 0. + if (m_dwMoveDuration == 0) + { + sys_err("MOVE_DURATION_FINAL_ZERO: name=%s vid=%u map=%ld base=%d dist=%.3f speed=%.3f", + GetName(), (DWORD)GetVID(), GetMapIndex(), baseMs, fDist, motionSpeed); + + m_dwMoveDuration = 1; + } if (IsNPC()) sys_log(1, "%s: GOTO: distance %f, spd %u, duration %u, motion speed %f pos %d %d -> %d %d", diff --git a/src/game/char_state.cpp b/src/game/char_state.cpp index 57f17b2..9bb16ba 100644 --- a/src/game/char_state.cpp +++ b/src/game/char_state.cpp @@ -761,11 +761,37 @@ bool __CHARACTER_GotoNearTarget(LPCHARACTER self, LPCHARACTER victim) void CHARACTER::StateMove() { + // SAFETY NET: + // Never allow division by zero or NaN/INF interpolation rates. + // Without this, NaN can become INT_MIN when cast to int and break sectree lookup (disconnect). + if (m_dwMoveDuration == 0) + { + sys_err("STATE_MOVE_ZERO_DURATION: name=%s vid=%u map=%ld start=(%d,%d) dest=(%d,%d)", + GetName(), (DWORD)GetVID(), GetMapIndex(), (int)m_posStart.x, (int)m_posStart.y, (int)m_posDest.x, (int)m_posDest.y); + + // Snap to destination and stop to keep server state consistent. + Move(m_posDest.x, m_posDest.y); + Stop(); + return; + } + DWORD dwElapsedTime = get_dword_time() - m_dwMoveStartTime; - float fRate = (float) dwElapsedTime / (float) m_dwMoveDuration; + float fRate = (float)dwElapsedTime / (float)m_dwMoveDuration; + + if (!std::isfinite(fRate)) + { + sys_err("STATE_MOVE_NONFINITE_RATE: name=%s vid=%u map=%ld elapsed=%u duration=%u", + GetName(), (DWORD)GetVID(), GetMapIndex(), dwElapsedTime, m_dwMoveDuration); + + Move(m_posDest.x, m_posDest.y); + Stop(); + return; + } if (fRate > 1.0f) fRate = 1.0f; + else if (fRate < 0.0f) + fRate = 0.0f; int x = (int) ((float) (m_posDest.x - m_posStart.x) * fRate + m_posStart.x); int y = (int) ((float) (m_posDest.y - m_posStart.y) * fRate + m_posStart.y); diff --git a/src/game/input_main.cpp b/src/game/input_main.cpp index 53add09..1c9e414 100644 --- a/src/game/input_main.cpp +++ b/src/game/input_main.cpp @@ -1696,7 +1696,7 @@ void CInputMain::Move(LPCHARACTER ch, const char * data) pack.dwVID = ch->GetVID(); pack.lX = pinfo->lX; pack.lY = pinfo->lY; - pack.dwTime = pinfo->dwTime; + pack.dwTime = get_dword_time(); pack.dwDuration = (pinfo->bFunc == FUNC_MOVE) ? ch->GetCurrentMoveDuration() : 0; ch->PacketAround(&pack, sizeof(TPacketGCMove), ch);