diff --git a/src/game/battle.cpp b/src/game/battle.cpp index ef26ec7..11a83a1 100644 --- a/src/game/battle.cpp +++ b/src/game/battle.cpp @@ -21,6 +21,7 @@ #include "sectree.h" #include "ani.h" #include "locale_service.h" +#include "skill.h" int battle_hit(LPCHARACTER ch, LPCHARACTER victim, int & iRetDam); @@ -60,6 +61,51 @@ bool battle_distance_valid(LPCHARACTER ch, LPCHARACTER victim) return battle_distance_valid_by_xy(ch->GetX(), ch->GetY(), victim->GetX(), victim->GetY()); } +int battle_get_ranged_target_max_distance(LPCHARACTER ch, BYTE attackType) +{ + int maxDistance = 3500; + + if (!ch) + return maxDistance; + + if (attackType == 0) + { + maxDistance += MAX(0, ch->GetPoint(POINT_BOW_DISTANCE)) * 100; + return maxDistance; + } + + if (attackType > 1) + { + if (CSkillProto* pkSk = CSkillManager::instance().Get(attackType)) + { + if (pkSk->dwTargetRange > 0) + return pkSk->dwTargetRange + 300; + } + } + + if (!ch->IsPC()) + maxDistance = MAX(maxDistance, static_cast(ch->GetMobAttackRange() * 1.5f)); + + return maxDistance; +} + +bool battle_ranged_target_valid(LPCHARACTER ch, LPCHARACTER victim, BYTE attackType, int* outDistance, int* outMaxDistance) +{ + if (!ch || !victim) + return false; + + const int distance = DISTANCE_APPROX(ch->GetX() - victim->GetX(), ch->GetY() - victim->GetY()); + const int maxDistance = battle_get_ranged_target_max_distance(ch, attackType); + + if (outDistance) + *outDistance = distance; + + if (outMaxDistance) + *outMaxDistance = maxDistance; + + return distance <= maxDistance; +} + bool timed_event_cancel(LPCHARACTER ch) { if (ch->m_pkTimedEvent) diff --git a/src/game/battle.h b/src/game/battle.h index 0537af5..e1d73d1 100644 --- a/src/game/battle.h +++ b/src/game/battle.h @@ -24,6 +24,8 @@ extern void battle_end(LPCHARACTER ch); extern bool battle_distance_valid_by_xy(long x, long y, long tx, long ty); extern bool battle_distance_valid(LPCHARACTER ch, LPCHARACTER victim); +extern int battle_get_ranged_target_max_distance(LPCHARACTER ch, BYTE attackType); +extern bool battle_ranged_target_valid(LPCHARACTER ch, LPCHARACTER victim, BYTE attackType, int* outDistance = nullptr, int* outMaxDistance = nullptr); extern int battle_count_attackers(LPCHARACTER ch); extern void NormalAttackAffect(LPCHARACTER pkAttacker, LPCHARACTER pkVictim); diff --git a/src/game/char_battle.cpp b/src/game/char_battle.cpp index 8187bac..248280c 100644 --- a/src/game/char_battle.cpp +++ b/src/game/char_battle.cpp @@ -2990,10 +2990,23 @@ class CFuncShoot if (!battle_is_attackable(m_me, pkVictim)) return; - if (m_me->IsNPC()) + int rangedDistance = 0; + int rangedLimit = 0; + if (!battle_ranged_target_valid(m_me, pkVictim, m_bType, &rangedDistance, &rangedLimit)) { - if (DISTANCE_APPROX(m_me->GetX() - pkVictim->GetX(), m_me->GetY() - pkVictim->GetY()) > 5000) - return; + if (m_me->IsPC()) + { + char szDetail[160]; + snprintf(szDetail, + sizeof(szDetail), + "type=%u distance=%d max=%d victim=%u", + m_bType, + rangedDistance, + rangedLimit, + pkVictim->GetVID()); + m_me->RecordAntiCheatViolation("RANGED_RANGE", rangedDistance > rangedLimit + 800 ? 18 : 8, szDetail, rangedDistance > rangedLimit + 800); + } + return; } LPITEM pkBow, pkArrow; @@ -3275,6 +3288,12 @@ void CHARACTER::FlyTarget(DWORD dwTargetVID, long x, long y, uint16_t wHeader) LPCHARACTER pkVictim = CHARACTER_MANAGER::instance().Find(dwTargetVID); TPacketGCFlyTargeting pack; + if (IsPC() && !pkVictim) + { + sys_log(1, "FlyTarget reject empty player target %s vid %u x %ld y %ld", GetName(), dwTargetVID, x, y); + return; + } + //pack.header = GC::FLY_TARGETING; pack.header = (wHeader == CG::FLY_TARGETING) ? GC::FLY_TARGETING : GC::ADD_FLY_TARGETING; pack.length = sizeof(pack); diff --git a/src/game/input_main.cpp b/src/game/input_main.cpp index 20f3569..6d70044 100644 --- a/src/game/input_main.cpp +++ b/src/game/input_main.cpp @@ -2092,6 +2092,49 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt void CInputMain::FlyTarget(LPCHARACTER ch, const char * pcData, uint16_t wHeader) { TPacketCGFlyTargeting * p = (TPacketCGFlyTargeting *) pcData; + + if (ch && ch->IsPC()) + { + LPCHARACTER victim = CHARACTER_MANAGER::instance().Find(p->dwTargetVID); + + if (!victim) + { + sys_log(1, "FLY_TARGET_INVALID: %s target=%u missing", ch->GetName(), p->dwTargetVID); + return; + } + + if (ch == victim) + { + char szDetail[128]; + snprintf(szDetail, sizeof(szDetail), "target=%u x=%ld y=%ld", p->dwTargetVID, static_cast(p->x), static_cast(p->y)); + ch->RecordAntiCheatViolation("FLY_TARGET_INVALID", 4, szDetail); + return; + } + + switch (victim->GetCharType()) + { + case CHAR_TYPE_WARP: + case CHAR_TYPE_GOTO: + ch->RecordAntiCheatViolation("FLY_TARGET_INVALID", 6, victim->GetName(), true); + return; + } + + int distance = 0; + int maxDistance = 0; + if (!battle_ranged_target_valid(ch, victim, 0, &distance, &maxDistance)) + { + char szDetail[160]; + snprintf(szDetail, + sizeof(szDetail), + "target=%s distance=%d max=%d", + victim->GetName(), + distance, + maxDistance); + ch->RecordAntiCheatViolation("FLY_TARGET_RANGE", distance > maxDistance + 800 ? 12 : 4, szDetail, distance > maxDistance + 800); + return; + } + } + ch->FlyTarget(p->dwTargetVID, p->x, p->y, wHeader); }