Skip to content

Commit 390f3a0

Browse files
Cheater_Detection:
1 parent 3a67b06 commit 390f3a0

4 files changed

Lines changed: 86 additions & 30 deletions

File tree

Cheater_Detection/Core/Evidence_system.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ local function getPrimaryMethod(evidence)
307307
primaryMethod = "Double Tap"
308308
elseif method == "anti_aim" then
309309
primaryMethod = "Anti-Aim"
310+
elseif method == "duck_speed" then
311+
primaryMethod = "Duck Speed"
312+
elseif method == "bhop" then
313+
primaryMethod = "Bhop"
310314
end
311315
end
312316
end

Cheater_Detection/Main.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ local function OnCreateMove(cmd)
511511

512512
if enableChoke then
513513
Profiler.Begin("FakeLag")
514+
FakeLag.BeginDetectionTick(curTick)
514515
for _, pState in ipairs(activePlayers) do
515516
if FakeLag.HasWork(pState) then
516517
FakeLag.ProcessPlayer(pState)

Cheater_Detection/detectors/duck_speed.lua

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
--[[ detectors/duck_speed.lua
22
Detects players moving too fast while fully ducked.
3-
Must be grounded and fully crouched for 2 seconds to avoid false positives.
3+
Must be grounded and fully crouched for ~3s before scoring (avoids brief crouch bursts).
44
Uses lazy PlayerData - NO direct entity API calls.
55
]]
66

@@ -15,15 +15,24 @@ local PlayerCache = require("Cheater_Detection.Core.player_cach
1515
local DuckSpeed = {}
1616

1717
local FULLY_CROUCHED_VIEW_OFFSET_Z = 45
18-
local DUCK_SPEED_EVIDENCE_WEIGHT = 85.0
18+
local DUCK_SPEED_RATIO_MIN = 0.66
19+
local DUCK_CONFIRM_SECONDS = 3.0
20+
local DUCK_SPEED_EVIDENCE_WEIGHT = 15.0
21+
local DUCK_SPEED_EVIDENCE_COOLDOWN_S = 10.0
22+
local DUCK_SPEED_EVIDENCE_CAP = Evidence.GetMethodScoreCap("duck_speed")
1923

2024
local tickCounters = {}
25+
local evidenceCooldowns = {}
2126

2227
local function isDuckSpeedEnabled()
2328
local adv = G.Menu and G.Menu.Advanced
2429
return adv and adv.DuckSpeed == true
2530
end
2631

32+
local function getConfirmTicks()
33+
return Constants.SecondsToTicks(DUCK_CONFIRM_SECONDS)
34+
end
35+
2736
function DuckSpeed.HasWork(playerState)
2837
if not isDuckSpeedEnabled() then
2938
return false
@@ -38,6 +47,9 @@ function DuckSpeed.HasWork(playerState)
3847
if not pdata or pdata.isDormant or not pdata.isAlive then
3948
return false
4049
end
50+
if Evidence.GetMethodWeight(playerState.id, "duck_speed") >= DUCK_SPEED_EVIDENCE_CAP then
51+
return Common.IsLogCategoryEnabled("All")
52+
end
4153
return true
4254
end
4355

@@ -66,6 +78,15 @@ function DuckSpeed.ProcessPlayer(playerState)
6678
return
6779
end
6880

81+
if Evidence.GetMethodWeight(id, "duck_speed") >= DUCK_SPEED_EVIDENCE_CAP then
82+
return
83+
end
84+
85+
local now = globals.RealTime()
86+
if evidenceCooldowns[id] and (now - evidenceCooldowns[id]) < DUCK_SPEED_EVIDENCE_COOLDOWN_S then
87+
return
88+
end
89+
6990
if not tickCounters[id] then
7091
tickCounters[id] = 0
7192
end
@@ -84,16 +105,20 @@ function DuckSpeed.ProcessPlayer(playerState)
84105
local maxSpeed = ent:GetPropFloat("m_flMaxspeed")
85106
local currentSpeed = velocity:Length()
86107

87-
if currentSpeed >= (maxSpeed * 0.66) then
108+
if currentSpeed >= (maxSpeed * DUCK_SPEED_RATIO_MIN) then
88109
tickCounters[id] = tickCounters[id] + 1
89110

90-
if tickCounters[id] >= Constants.SecondsToTicks(2) then
111+
if tickCounters[id] >= getConfirmTicks() then
112+
local beforeWeight = Evidence.GetMethodWeight(id, "duck_speed")
91113
Evidence.AddEvidence(id, "duck_speed", DUCK_SPEED_EVIDENCE_WEIGHT)
92114
tickCounters[id] = 0
115+
evidenceCooldowns[id] = now
93116
if Common.IsDebugEnabled() then
94117
print(string.format(
95-
"[DuckSpeed] %s duck speed exploit (evidence +%.1f)",
96-
id, DUCK_SPEED_EVIDENCE_WEIGHT))
118+
"[DuckSpeed] %s duck speed (evidence +%.1f, total duck_speed=%.1f)",
119+
id,
120+
Evidence.GetMethodWeight(id, "duck_speed") - beforeWeight,
121+
Evidence.GetMethodWeight(id, "duck_speed")))
97122
end
98123
end
99124
else
@@ -106,10 +131,12 @@ end
106131

107132
Events.Subscribe("OnPlayerDisconnect", function(id)
108133
tickCounters[id] = nil
134+
evidenceCooldowns[id] = nil
109135
end)
110136

111137
Events.Subscribe("OnPlayerRemoved", function(id)
112138
tickCounters[id] = nil
139+
evidenceCooldowns[id] = nil
113140
end)
114141

115142
return DuckSpeed

Cheater_Detection/detectors/fake_lag.lua

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ local FPS_ROLLING_WINDOW_S = 1.0 -- 1s
6363
local DEBUG_SUMMARY_INTERVAL_S = 1.0
6464
local DEBUG_DELTA_WINDOW = 16 -- newest simtime gaps to classify for Choke logs
6565
local DEBUG_SAMPLE_TICK_INTERVAL = 4 -- Choke log rollup: full window snapshot every N ticks
66+
local FL_PLAYER_PHASE_COUNT = 4 -- stagger players across ticks (was: all on tick%4)
6667
-- Newest simtime gaps to read; clamped to DetectionConfig simtime retention (see cd_simhistory).
6768
local FAKE_LAG_EVIDENCE_CAP = Evidence.GetMethodScoreCap("fake_lag") -- suspicious-only tuning cap
6869
-- Below ~40 fps, frame gaps can exceed the 8-tick choke threshold (tickrate/fps). No listen/debug bypass.
@@ -75,6 +76,14 @@ local debugSummaries = {}
7576
local smoothedFrameTime = 1 / 60
7677
local rollingFrameSamples = {} -- { t, ft } within FPS_ROLLING_WINDOW_S
7778
local _positiveDeltaBuf = {}
79+
local cachedGapMaxTicks = 23
80+
local flTickCache = {
81+
curTick = -1,
82+
blockReason = nil,
83+
flScan = 20,
84+
isChokeDebug = false,
85+
playerPhase = 0,
86+
}
7887

7988
-- Pre-HistoryManager, delta lists only had positive simtime steps (stalls omitted).
8089
-- Scoring thresholds assume that shape; full buffer (with 0t holds) is for stall/burst paths only.
@@ -232,41 +241,56 @@ function FakeLag.IsDetectionAllowed()
232241
return FakeLag.GetDetectionBlockReason() == nil
233242
end
234243

244+
--- Once per game tick before the player loop (Main.lua). Caches FPS gate, scan depth, gap cap.
245+
function FakeLag.BeginDetectionTick(curTick)
246+
if flTickCache.curTick == curTick then
247+
return
248+
end
249+
flTickCache.curTick = curTick
250+
flTickCache.playerPhase = curTick % FL_PLAYER_PHASE_COUNT
251+
flTickCache.isChokeDebug = Common.IsLogCategoryEnabled("Choke")
252+
flTickCache.flScan = DetectionConfig.GetSimtimeScanLimits().flScan
253+
cachedGapMaxTicks = DoubleTap.GetFakeLagMaxChokeTicks() + 2
254+
flTickCache.blockReason = FakeLag.GetDetectionBlockReason()
255+
end
256+
235257
function FakeLag.HasWork(playerState)
236258
if not playerState or not playerState.id then
237259
return false
238260
end
239-
if not FakeLag.IsDetectionAllowed() then
261+
local curTick = globals.TickCount()
262+
if flTickCache.curTick ~= curTick then
263+
FakeLag.BeginDetectionTick(curTick)
264+
end
265+
if flTickCache.blockReason then
240266
return false
241267
end
268+
269+
local entIndex = playerState.entityIndex or 0
270+
if (entIndex % FL_PLAYER_PHASE_COUNT) ~= flTickCache.playerPhase then
271+
return false
272+
end
273+
242274
if (playerState.flags & Constants.Flags.CHEATER) ~= 0 then
243-
return Common.IsLogCategoryEnabled("Choke")
275+
return flTickCache.isChokeDebug
244276
end
277+
245278
local id = playerState.id
246-
if Common.IsLogCategoryEnabled("Choke") then
247-
if Evidence.GetMethodWeight(id, "fake_lag") >= FAKE_LAG_EVIDENCE_CAP then
248-
return false
249-
end
250-
return true
251-
end
252279
if Evidence.GetMethodWeight(id, "fake_lag") >= FAKE_LAG_EVIDENCE_CAP then
253-
return false
280+
return flTickCache.isChokeDebug
254281
end
255-
return (globals.TickCount() % 4) == 0
282+
283+
return true
256284
end
257285

258286
local function getRhythmTolerance(anchorDelta)
259287
return math.max(RHYTHM_TOLERANCE_TICKS, math.floor(anchorDelta * 0.2 + 0.5))
260288
end
261289

262-
local function getFlGapMaxTicks()
263-
return DoubleTap.GetFakeLagMaxChokeTicks() + 2
264-
end
265-
266290
local function isFlSizedGap(tickDelta)
267291
return tickDelta
268292
and tickDelta >= MIN_FAKELAG_CHOKE_TICKS
269-
and tickDelta <= getFlGapMaxTicks()
293+
and tickDelta <= cachedGapMaxTicks
270294
end
271295

272296
local function countStallsInWindow(deltaTicks, sampleCount, deltaCount)
@@ -611,13 +635,11 @@ local function addCappedFakeLagEvidence(id, wantedWeight)
611635
return 0
612636
end
613637

614-
local function isActiveChokePattern(deltaTicks, deltaCount)
638+
local function isActiveChokePattern(deltaTicks, deltaCount, posDeltas, posCount)
615639
if getChokeHoldSignal(deltaTicks, RECENT_CHOKE_SAMPLE_COUNT, deltaCount) then
616640
return true
617641
end
618642

619-
local posDeltas, posCount = buildPositiveDeltaView(deltaTicks, deltaCount)
620-
621643
if getSustainedChokeAverage(posDeltas, posCount) then
622644
return true
623645
end
@@ -670,8 +692,11 @@ function FakeLag.ProcessPlayer(playerState)
670692
return
671693
end
672694

673-
local blockReason = FakeLag.GetDetectionBlockReason()
674-
if blockReason then
695+
local curTick = globals.TickCount()
696+
if flTickCache.curTick ~= curTick then
697+
FakeLag.BeginDetectionTick(curTick)
698+
end
699+
if flTickCache.blockReason then
675700
return
676701
end
677702

@@ -688,7 +713,7 @@ function FakeLag.ProcessPlayer(playerState)
688713
if history._count < 5 then return end
689714

690715
local now = globals.RealTime()
691-
local isDebug = Common.IsLogCategoryEnabled("Choke")
716+
local isDebug = flTickCache.isChokeDebug
692717

693718
if not isDebug then
694719
if Evidence.GetMethodWeight(id, "fake_lag") >= FAKE_LAG_EVIDENCE_CAP then
@@ -701,8 +726,7 @@ function FakeLag.ProcessPlayer(playerState)
701726
end
702727
end
703728

704-
local flScan = DetectionConfig.GetSimtimeScanLimits().flScan
705-
local deltaTicks, deltaCount, _sumTicks = HistoryManager.GetSimDeltaDeltas(history, flScan)
729+
local deltaTicks, deltaCount, _sumTicks = HistoryManager.GetSimDeltaDeltas(history, flTickCache.flScan)
706730
if deltaCount < 1 then
707731
return
708732
end
@@ -715,7 +739,7 @@ function FakeLag.ProcessPlayer(playerState)
715739
end
716740

717741
-- Ongoing choke: pause exploit decay so score does not drain between evidence cooldowns.
718-
if isActiveChokePattern(deltaTicks, deltaCount) then
742+
if isActiveChokePattern(deltaTicks, deltaCount, posDeltas, posCount) then
719743
Evidence.HoldDecayForMethod(id, "fake_lag")
720744
end
721745

0 commit comments

Comments
 (0)