Skip to content

Commit 3177f5d

Browse files
authored
fix(): False positives against speed hacks and countermeasures (#114)
1 parent 00e1399 commit 3177f5d

File tree

4 files changed

+107
-27
lines changed

4 files changed

+107
-27
lines changed

src/AnticheatData.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,31 @@
2727
AnticheatData::AnticheatData()
2828
{
2929
lastOpcode = 0;
30+
lastSpeedRate = 0.0f;
3031
totalReports = 0;
3132
for (uint8 i = 0; i < MAX_REPORT_TYPES; i++)
3233
{
3334
typeReports[i] = 0;
3435
tempReports[i] = 0;
3536
tempReportsTimer[i] = 0;
3637
}
37-
average = 0;
38+
average = 0.0f;
3839
creationTime = 0;
3940
hasDailyReport = false;
41+
justUsedMovementSpell = false;
4042
}
4143

4244
AnticheatData::~AnticheatData()
4345
{
4446
}
4547

48+
void AnticheatData::SetLastInformations(MovementInfo movementInfo, uint32 opcode, float speedRate)
49+
{
50+
SetLastMovementInfo(movementInfo);
51+
SetLastOpcode(opcode);
52+
SetLastSpeedRate(speedRate);
53+
}
54+
4655
void AnticheatData::SetDailyReportState(bool b)
4756
{
4857
hasDailyReport = b;

src/AnticheatData.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,17 @@ class AnticheatData
3535
AnticheatData();
3636
~AnticheatData();
3737

38+
void SetLastInformations(MovementInfo movementInfo, uint32 opcode, float speedRate);
39+
3840
void SetLastOpcode(uint32 opcode);
3941
uint32 GetLastOpcode() const;
4042

4143
const MovementInfo& GetLastMovementInfo() const;
4244
void SetLastMovementInfo(MovementInfo& moveInfo);
4345

46+
[[nodiscard]] float GetLastSpeedRate() const { return lastSpeedRate; }
47+
void SetLastSpeedRate(float speedRate) { lastSpeedRate = speedRate; }
48+
4449
void SetPosition(float x, float y, float z, float o);
4550

4651
uint32 GetTotalReports() const;
@@ -63,16 +68,21 @@ class AnticheatData
6368

6469
void SetDailyReportState(bool b);
6570
bool GetDailyReportState();
71+
72+
[[nodiscard]] bool GetJustUsedMovementSpell() const { return justUsedMovementSpell; }
73+
void SetJustUsedMovementSpell(bool value) { justUsedMovementSpell = value; }
6674
private:
6775
uint32 lastOpcode;
6876
MovementInfo lastMovementInfo;
77+
float lastSpeedRate;
6978
uint32 totalReports;
7079
uint32 typeReports[MAX_REPORT_TYPES];
7180
float average;
7281
uint32 creationTime;
7382
uint32 tempReports[MAX_REPORT_TYPES];
7483
uint32 tempReportsTimer[MAX_REPORT_TYPES];
7584
bool hasDailyReport;
85+
bool justUsedMovementSpell;
7686
};
7787

7888
#endif

src/AnticheatMgr.cpp

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ constexpr auto ALLOWED_ACK_LAG = 2000;
4141

4242
enum Spells : uint32
4343
{
44+
BLINK = 1953,
45+
BLINK_COOLDOWN_REDUCTION = 23025, // Reduces Blink cooldown by 2 seconds.
46+
GLYPH_OF_BLINK = 56365, // Increases Blink distance by 5 yards.
47+
SHADOWSTEP = 36554,
48+
FILTHY_TRICKS_RANK_1 = 58414, // Reduces Shadowstep cooldown by 5 seconds.
49+
FILTHY_TRICKS_RANK_2 = 58415, // Reduces Shadowstep cooldown by 10 seconds.
4450
SHACKLES = 38505,
4551
LFG_SPELL_DUNGEON_DESERTER = 71041,
4652
BG_SPELL_DESERTER = 26013,
@@ -93,8 +99,7 @@ void AnticheatMgr::StartHackDetection(Player* player, MovementInfo movementInfo,
9399

94100
if (player->IsInFlight() || player->GetTransport() || player->GetVehicle())
95101
{
96-
m_Players[key].SetLastMovementInfo(movementInfo);
97-
m_Players[key].SetLastOpcode(opcode);
102+
m_Players[key].SetLastInformations(movementInfo, opcode, GetPlayerCurrentSpeedRate(player));
98103
return;
99104
}
100105

@@ -127,8 +132,57 @@ void AnticheatMgr::StartHackDetection(Player* player, MovementInfo movementInfo,
127132
BGStartExploit(player, movementInfo);
128133
}
129134
}
130-
m_Players[key].SetLastMovementInfo(movementInfo);
131-
m_Players[key].SetLastOpcode(opcode);
135+
m_Players[key].SetLastInformations(movementInfo, opcode, GetPlayerCurrentSpeedRate(player));
136+
}
137+
138+
uint32 AnticheatMgr::GetTeleportSkillCooldownDurationInMS(Player* player) const
139+
{
140+
switch (player->getClass())
141+
{
142+
case CLASS_ROGUE:
143+
if (player->HasAura(FILTHY_TRICKS_RANK_2))
144+
return 20000u;
145+
else if (player->HasAura(FILTHY_TRICKS_RANK_1))
146+
return 25000u;
147+
return 30000u;
148+
case CLASS_MAGE:
149+
if (player->HasAura(BLINK_COOLDOWN_REDUCTION)) // Bonus from Vanilla/Early TBC pvp gear.
150+
return 13000u;
151+
return 15000u;
152+
default:
153+
return 0u;
154+
}
155+
}
156+
157+
float AnticheatMgr::GetTeleportSkillDistanceInYards(Player* player) const
158+
{
159+
switch (player->getClass())
160+
{
161+
case CLASS_ROGUE: // The rogue's teleport spell is Shadowstep.
162+
return 25.0f; // Synful-Syn: Help needed! At least, 25 yards adjustment is better than nothing!
163+
// The spell can be casted at a maximum of 25 yards from the middle of the ennemy and teleports the player a short distance behind the target which might be over 25 yards, especially when the target is facing the rogue.
164+
// Using Shadowstep on Onyxia at as far as I could moved me by 44 yards. Doing it on a blood elf in duel moved me 29 yards.
165+
case CLASS_MAGE: // The mage's teleport spell is Blink.
166+
if (player->HasAura(GLYPH_OF_BLINK))
167+
return 25.1f; // Includes a 0.1 miscalculation margin.
168+
return 20.1f; // Includes a 0.1 miscalculation margin.
169+
default:
170+
return 0.0f;
171+
}
172+
}
173+
174+
// Get how many yards the player can move in a second.
175+
float AnticheatMgr::GetPlayerCurrentSpeedRate(Player* player) const
176+
{
177+
// we need to know HOW is the player moving
178+
// TO-DO: Should we check the incoming movement flags?
179+
if (player->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING))
180+
return player->GetSpeed(MOVE_SWIM);
181+
else if (player->IsFlying())
182+
return player->GetSpeed(MOVE_FLIGHT);
183+
else if (player->HasUnitMovementFlag(MOVEMENTFLAG_WALKING))
184+
return player->GetSpeed(MOVE_WALK);
185+
return player->GetSpeed(MOVE_RUN);
132186
}
133187

134188
void AnticheatMgr::SpeedHackDetection(Player* player, MovementInfo movementInfo)
@@ -179,32 +233,20 @@ void AnticheatMgr::SpeedHackDetection(Player* player, MovementInfo movementInfo)
179233
}
180234
}
181235

182-
uint32 distance2D = (uint32)movementInfo.pos.GetExactDist2d(&m_Players[key].GetLastMovementInfo().pos);
236+
float distance2D = movementInfo.pos.GetExactDist2d(&m_Players[key].GetLastMovementInfo().pos);
183237

184238
// We don't need to check for a speedhack if the player hasn't moved
185239
// This is necessary since MovementHandler fires if you rotate the camera in place
186240
if (!distance2D)
187241
return;
188242

189-
// we need to know HOW is the player moving
190-
// TO-DO: Should we check the incoming movement flags?
191-
UnitMoveType moveType;
192-
if (player->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING))
193-
moveType = MOVE_SWIM;
194-
else if (player->IsFlying())
195-
moveType = MOVE_FLIGHT;
196-
else if (player->HasUnitMovementFlag(MOVEMENTFLAG_WALKING))
197-
moveType = MOVE_WALK;
198-
else
199-
moveType = MOVE_RUN;
200-
201-
// how many yards the player can do in one sec.
202-
// We remove the added speed for jumping because otherwise permanently jumping doubles your allowed speed
203-
uint32 speedRate = (uint32)(player->GetSpeed(moveType));
204-
205243
// how long the player took to move to here.
206244
uint32 timeDiff = getMSTimeDiff(m_Players[key].GetLastMovementInfo().time, movementInfo.time);
207245

246+
float speedRate = GetPlayerCurrentSpeedRate(player);
247+
if (timeDiff <= ALLOWED_ACK_LAG)
248+
speedRate = std::max(speedRate, m_Players[key].GetLastSpeedRate()); // The player might have been moving with a previously faster speed. This should help mitigate a false positive from loosing a speed increase buff.
249+
208250
if (int32(timeDiff) < 0 && sConfigMgr->GetOption<bool>("Anticheat.CM.TIMEMANIPULATION", true))
209251
{
210252
if (sConfigMgr->GetOption<bool>("Anticheat.CM.WriteLog", true))
@@ -257,19 +299,36 @@ void AnticheatMgr::SpeedHackDetection(Player* player, MovementInfo movementInfo)
257299
BuildReport(player, COUNTER_MEASURES_REPORT);
258300
}
259301

302+
// Adjust distance from Blink/Shadowstep.
303+
if (player->HasAura(BLINK) || player->HasAura(SHADOWSTEP))
304+
{
305+
// Only adjust the travelled distance if the player previously didn't use a movement spell or didn't move at all since they previously used the movement spell.
306+
if (!m_Players[key].GetJustUsedMovementSpell() || timeDiff >= GetTeleportSkillCooldownDurationInMS(player))
307+
{
308+
m_Players[key].SetJustUsedMovementSpell(true);
309+
distance2D = std::max(distance2D - GetTeleportSkillDistanceInYards(player), 0.0f);
310+
}
311+
}
312+
else
313+
{
314+
m_Players[key].SetJustUsedMovementSpell(false);
315+
}
316+
260317
// this is the distance doable by the player in 1 sec, using the time done to move to this point.
261-
uint32 clientSpeedRate = distance2D * 1000 / timeDiff;
318+
float clientSpeedRate = 0.0f;
319+
if (float floatTimeDiff = float(timeDiff))
320+
clientSpeedRate = distance2D * 1000.0f / floatTimeDiff;
262321

263322
// we create a diff speed in uint32 for further precision checking to avoid legit fall and slide
264-
uint32 diffspeed = clientSpeedRate - speedRate;
323+
float diffspeed = clientSpeedRate - speedRate;
265324

266325
// create a conf to establish a speed limit tolerance over server rate set speed
267326
// this is done so we can ignore minor violations that are not false positives such as going 1 or 2 over the speed limit
268-
_assignedspeeddiff = sConfigMgr->GetOption<uint32>("Anticheat.SpeedLimitTolerance", 0);
327+
float assignedspeeddiff = sConfigMgr->GetOption<float>("Anticheat.SpeedLimitTolerance", 0.0f);
269328

270329
// We did the (uint32) cast to accept a margin of tolerance for seasonal spells and buffs such as sugar rush
271330
// We check the last MovementInfo for the falling flag since falling down a hill and sliding a bit triggered a false positive
272-
if ((diffspeed >= _assignedspeeddiff) && !m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_FALLING))
331+
if ((diffspeed >= assignedspeeddiff) && !m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_FALLING))
273332
{
274333
if (clientSpeedRate > speedRate * 1.05f)
275334
{

src/AnticheatMgr.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,11 @@ class AnticheatMgr
131131
void BGStartExploit(Player* player, MovementInfo movementInfo);
132132
void BuildReport(Player* player, ReportTypes reportType);
133133
bool MustCheckTempReports(ReportTypes type);
134+
[[nodiscard]] uint32 GetTeleportSkillCooldownDurationInMS(Player* player) const;
135+
[[nodiscard]] float GetTeleportSkillDistanceInYards(Player* player) const;
136+
[[nodiscard]] float GetPlayerCurrentSpeedRate(Player* player) const;
134137
uint32 _counter = 0;
135138
uint32 _alertFrequency = 0;
136-
uint32 _assignedspeeddiff = 0;
137139
uint32 _updateCheckTimer = 4000;
138140
uint32 m_MapId;
139141
std::array<Position, PVP_TEAMS_COUNT> _startPosition;

0 commit comments

Comments
 (0)