From acf7038abbb924e2aa71ee9d802889eab34778a4 Mon Sep 17 00:00:00 2001 From: server Date: Thu, 16 Apr 2026 14:31:37 +0200 Subject: [PATCH] Add combat target reacquire telemetry --- src/game/char.cpp | 10 ++++ src/game/char.h | 10 ++++ src/game/char_battle.cpp | 116 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/src/game/char.cpp b/src/game/char.cpp index 93fd167..031ed31 100644 --- a/src/game/char.cpp +++ b/src/game/char.cpp @@ -161,6 +161,16 @@ void CHARACTER::Initialize() m_iFishingPatternTightSequence = 0; m_dwMiningWindowStart = 0; m_iMiningWindowCount = 0; + m_dwCombatPatternWindowStart = 0; + m_dwLastCombatPatternTime = 0; + m_dwLastCombatPatternVictimVID = 0; + m_lLastCombatPatternVictimX = 0; + m_lLastCombatPatternVictimY = 0; + m_iCombatPatternSampleCount = 0; + m_iCombatPatternMinIntervalMs = 0; + m_iCombatPatternMaxIntervalMs = 0; + m_iCombatPatternMinStep = 0; + m_iCombatPatternMaxStep = 0; m_iLastPMPulse = 0; m_iPMCounter = 0; diff --git a/src/game/char.h b/src/game/char.h index 9a7a913..763b935 100644 --- a/src/game/char.h +++ b/src/game/char.h @@ -2073,6 +2073,16 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider int m_iFishingPatternTightSequence; DWORD m_dwMiningWindowStart; int m_iMiningWindowCount; + DWORD m_dwCombatPatternWindowStart; + DWORD m_dwLastCombatPatternTime; + DWORD m_dwLastCombatPatternVictimVID; + long m_lLastCombatPatternVictimX; + long m_lLastCombatPatternVictimY; + int m_iCombatPatternSampleCount; + int m_iCombatPatternMinIntervalMs; + int m_iCombatPatternMaxIntervalMs; + int m_iCombatPatternMinStep; + int m_iCombatPatternMaxStep; void ClearPMCounter(void) { m_iPMCounter = 0; } void IncreasePMCounter(void) { m_iPMCounter++; } void SetLastPMPulse(void); diff --git a/src/game/char_battle.cpp b/src/game/char_battle.cpp index 248280c..313b0c6 100644 --- a/src/game/char_battle.cpp +++ b/src/game/char_battle.cpp @@ -41,6 +41,119 @@ #include #include +namespace +{ + constexpr DWORD kCombatPatternWindowMs = 10 * 60 * 1000; + constexpr int kCombatPatternMinSamples = 10; + constexpr int kCombatPatternMaxIntervalMs = 8000; + constexpr int kCombatPatternMaxIntervalSpreadMs = 280; + constexpr int kCombatPatternMaxStepSpread = 480; + constexpr int kCombatPatternMinRouteStep = 250; + + void ResetCombatPatternWindow(LPCHARACTER ch, LPCHARACTER victim, DWORD now) + { + if (!ch) + return; + + ch->m_dwCombatPatternWindowStart = now; + ch->m_dwLastCombatPatternTime = now; + ch->m_dwLastCombatPatternVictimVID = victim ? victim->GetVID() : 0; + ch->m_lLastCombatPatternVictimX = victim ? victim->GetX() : 0; + ch->m_lLastCombatPatternVictimY = victim ? victim->GetY() : 0; + ch->m_iCombatPatternSampleCount = 0; + ch->m_iCombatPatternMinIntervalMs = 0; + ch->m_iCombatPatternMaxIntervalMs = 0; + ch->m_iCombatPatternMinStep = 0; + ch->m_iCombatPatternMaxStep = 0; + } + + void ObserveCombatTargetPattern(LPCHARACTER ch, LPCHARACTER victim, DWORD now) + { + if (!ch || !victim || !ch->IsPC()) + return; + + if (!victim->IsMonster() && !victim->IsStone()) + return; + + if (0 == ch->m_dwCombatPatternWindowStart || now - ch->m_dwCombatPatternWindowStart >= kCombatPatternWindowMs) + { + ResetCombatPatternWindow(ch, victim, now); + return; + } + + if (0 == ch->m_dwLastCombatPatternVictimVID) + { + ResetCombatPatternWindow(ch, victim, now); + return; + } + + if (ch->m_dwLastCombatPatternVictimVID == victim->GetVID()) + { + ch->m_dwLastCombatPatternTime = now; + ch->m_lLastCombatPatternVictimX = victim->GetX(); + ch->m_lLastCombatPatternVictimY = victim->GetY(); + return; + } + + const int iIntervalMs = static_cast(now - ch->m_dwLastCombatPatternTime); + const int iStep = DISTANCE_APPROX(victim->GetX() - ch->m_lLastCombatPatternVictimX, + victim->GetY() - ch->m_lLastCombatPatternVictimY); + + ch->m_dwLastCombatPatternTime = now; + ch->m_dwLastCombatPatternVictimVID = victim->GetVID(); + ch->m_lLastCombatPatternVictimX = victim->GetX(); + ch->m_lLastCombatPatternVictimY = victim->GetY(); + + if (iIntervalMs <= 0 || iIntervalMs > kCombatPatternMaxIntervalMs) + { + ResetCombatPatternWindow(ch, victim, now); + return; + } + + if (0 == ch->m_iCombatPatternSampleCount) + { + ch->m_iCombatPatternSampleCount = 1; + ch->m_iCombatPatternMinIntervalMs = iIntervalMs; + ch->m_iCombatPatternMaxIntervalMs = iIntervalMs; + ch->m_iCombatPatternMinStep = iStep; + ch->m_iCombatPatternMaxStep = iStep; + return; + } + + ++ch->m_iCombatPatternSampleCount; + ch->m_iCombatPatternMinIntervalMs = MIN(ch->m_iCombatPatternMinIntervalMs, iIntervalMs); + ch->m_iCombatPatternMaxIntervalMs = MAX(ch->m_iCombatPatternMaxIntervalMs, iIntervalMs); + ch->m_iCombatPatternMinStep = MIN(ch->m_iCombatPatternMinStep, iStep); + ch->m_iCombatPatternMaxStep = MAX(ch->m_iCombatPatternMaxStep, iStep); + + if (ch->m_iCombatPatternSampleCount < kCombatPatternMinSamples) + return; + + const int iIntervalSpread = ch->m_iCombatPatternMaxIntervalMs - ch->m_iCombatPatternMinIntervalMs; + const int iStepSpread = ch->m_iCombatPatternMaxStep - ch->m_iCombatPatternMinStep; + if (iIntervalSpread > kCombatPatternMaxIntervalSpreadMs || + iStepSpread > kCombatPatternMaxStepSpread || + ch->m_iCombatPatternMaxStep < kCombatPatternMinRouteStep) + return; + + char szDetail[160]; + snprintf(szDetail, + sizeof(szDetail), + "samples=%d interval=%d..%d step=%d..%d target=%u", + ch->m_iCombatPatternSampleCount, + ch->m_iCombatPatternMinIntervalMs, + ch->m_iCombatPatternMaxIntervalMs, + ch->m_iCombatPatternMinStep, + ch->m_iCombatPatternMaxStep, + victim->GetVID()); + ch->RecordAntiCheatViolation("TARGET_REACQUIRE_PATTERN", + iIntervalSpread <= 140 && iStepSpread <= 280 ? 2 : 1, + szDetail); + + ResetCombatPatternWindow(ch, victim, now); + } +} + DWORD AdjustExpByLevel(const LPCHARACTER ch, const DWORD exp) { if (PLAYER_EXP_TABLE_MAX < ch->GetLevel()) @@ -271,6 +384,9 @@ bool CHARACTER::Attack(LPCHARACTER pkVictim, BYTE bType) // sys_log(0, "%s Attack %s type %u ret %d", GetName(), pkVictim->GetName(), bType, iRet); if (iRet == BATTLE_DAMAGE || iRet == BATTLE_DEAD) { + if (IsPC() && 0 == bType) + ObserveCombatTargetPattern(this, pkVictim, dwCurrentTime); + OnMove(true); pkVictim->OnMove();