Skip to content

Commit 4e8426f

Browse files
fix
1 parent 3622aa3 commit 4e8426f

6 files changed

Lines changed: 245 additions & 76 deletions

File tree

.github/workflows/release.yml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Build bundled Cheater_Detection.lua + local HTTP bridge, tag with auto version bump, publish GitHub Release.
2+
# No workflow artifacts are uploaded (release assets only) to limit Actions storage use.
3+
4+
name: Release
5+
6+
on:
7+
push:
8+
branches:
9+
- main
10+
11+
concurrency:
12+
group: release-${{ github.repository }}
13+
cancel-in-progress: true
14+
15+
permissions:
16+
contents: write
17+
actions: write
18+
19+
jobs:
20+
release:
21+
if: >-
22+
github.event_name == 'push' &&
23+
github.ref == 'refs/heads/main' &&
24+
!contains(github.event.head_commit.message, '[skip release]')
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 30
27+
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 0
33+
34+
- name: Compute next version tag
35+
id: version
36+
shell: bash
37+
run: |
38+
set -euo pipefail
39+
latest_tag="$(git tag -l 'v[0-9]*' --sort=-v:refname | head -n1 || true)"
40+
if [[ -z "${latest_tag}" ]]; then
41+
next="v1.0"
42+
else
43+
ver="${latest_tag#v}"
44+
if [[ "${ver}" =~ ^[0-9]+\.[0-9]+$ ]]; then
45+
IFS='.' read -r major minor <<< "${ver}"
46+
next="v${major}.$((minor + 1))"
47+
else
48+
IFS='.' read -r major minor patch _ <<< "${ver}."
49+
major="${major:-0}"
50+
minor="${minor:-0}"
51+
patch="${patch:-0}"
52+
next="v${major}.${minor}.$((patch + 1))"
53+
fi
54+
fi
55+
echo "latest=${latest_tag:-none}" >> "$GITHUB_OUTPUT"
56+
echo "tag=${next}" >> "$GITHUB_OUTPUT"
57+
echo "Next release tag: ${next} (previous: ${latest_tag:-none})"
58+
59+
- name: Setup Node.js
60+
uses: actions/setup-node@v4
61+
with:
62+
node-version: "20"
63+
cache: npm
64+
65+
- name: Setup Python
66+
uses: actions/setup-python@v5
67+
with:
68+
python-version: "3.12"
69+
70+
- name: Install Node dependencies
71+
run: npm ci
72+
73+
- name: Rebuild embedded databases
74+
run: python external_sources/rebuild_embedded_databases.py
75+
76+
- name: Bundle Lua
77+
env:
78+
BUNDLE_CI: "1"
79+
BUNDLE_OUTPUT_PATH: ${{ github.workspace }}/dist/Cheater_Detection.lua
80+
run: npm run bundle
81+
82+
- name: Stage release files
83+
run: |
84+
set -euo pipefail
85+
mkdir -p dist
86+
test -s dist/Cheater_Detection.lua
87+
cp LocalBridge/local_http_bridge_server.py dist/local_http_bridge_server.py
88+
sha256sum dist/Cheater_Detection.lua dist/local_http_bridge_server.py > dist/SHA256SUMS.txt
89+
90+
- name: Create GitHub Release
91+
env:
92+
GH_TOKEN: ${{ github.token }}
93+
run: |
94+
set -euo pipefail
95+
tag="${{ steps.version.outputs.tag }}"
96+
short_sha="${GITHUB_SHA::7}"
97+
body_file="$(mktemp)"
98+
{
99+
echo "Automated release for commit [\`${short_sha}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA})."
100+
echo ""
101+
echo "### Assets"
102+
echo "- \`Cheater_Detection.lua\` — bundled Lmaobox script (load from \`%localappdata%/lua/\`)"
103+
echo "- \`local_http_bridge_server.py\` — optional localhost HTTP bridge"
104+
echo "- \`SHA256SUMS.txt\` — checksums"
105+
echo ""
106+
echo "### Install"
107+
echo "1. Download \`Cheater_Detection.lua\` from this release."
108+
echo "2. Place it in \`%localappdata%/lua/Cheater_Detection.lua\`."
109+
echo "3. (Optional) Run \`local_http_bridge_server.py\` with Python 3 for async lookups."
110+
} > "${body_file}"
111+
gh release create "${tag}" \
112+
--title "${tag}" \
113+
--notes-file "${body_file}" \
114+
dist/Cheater_Detection.lua \
115+
dist/local_http_bridge_server.py \
116+
dist/SHA256SUMS.txt
117+
118+
- name: Purge workflow artifacts
119+
uses: kolpav/purge-artifacts-action@v2
120+
with:
121+
token: ${{ github.token }}
122+
max-entries: 0

Cheater_Detection/Main.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ local function OnCreateMove(cmd)
448448
for _, pState in ipairs(activePlayers) do
449449
enforceValveAutoDisconnect(pState)
450450
if sessionState.valveDisconnectTriggered then
451+
Profiler.End("ValveCheck")
451452
Profiler.End("PlayerScan_Loop")
452453
Profiler.End("CreateMove_Total")
453454
return

Cheater_Detection/detectors/bhop.lua

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
--[[ detectors/bhop.lua
22
Detects scripted bunnyhops by counting consecutive "perfect" jumps.
33
Tracks ground contact ticks on landing, then scores on ground→air transition.
4+
Only runs while airborne or in the brief post-landing ground window (no idle ground work).
45
]]
56

67
local Constants = require("Cheater_Detection.Core.constants")
@@ -24,6 +25,11 @@ local function isBhopEnabled()
2425
return adv and adv.Bhop == true
2526
end
2627

28+
local function clearBhopState(id)
29+
playerData[id] = nil
30+
end
31+
32+
--- Airborne this tick, or still counting landing ticks after air (inAir / groundTicks only set during chains).
2733
function Bhop.HasWork(playerState)
2834
if not isBhopEnabled() then
2935
return false
@@ -34,10 +40,18 @@ function Bhop.HasWork(playerState)
3440
if (playerState.flags & Constants.Flags.CHEATER) ~= 0 then
3541
return false
3642
end
37-
if playerState.pdata and (playerState.pdata.isDormant or not playerState.pdata.isAlive) then
43+
local pdata = playerState.pdata
44+
if not pdata or pdata.isDormant or not pdata.isAlive then
45+
return false
46+
end
47+
if pdata.onGround == nil then
3848
return false
3949
end
40-
return true
50+
if not pdata.onGround then
51+
return true
52+
end
53+
local data = playerData[playerState.id]
54+
return data ~= nil and (data.inAir == true or data.groundTicks ~= nil)
4155
end
4256

4357
function Bhop.ProcessPlayer(playerState)
@@ -51,22 +65,44 @@ function Bhop.ProcessPlayer(playerState)
5165

5266
local id = playerState.id
5367
local pdata = playerState.pdata
54-
5568
local onGround = pdata.onGround
5669

57-
if onGround == nil then
70+
if id == PlayerCache.GetLocalID() and not Common.IsDebugEnabled() then
5871
return
5972
end
6073

61-
if id == PlayerCache.GetLocalID() and not Common.IsDebugEnabled() then
74+
if onGround then
75+
local data = playerData[id]
76+
if not data then
77+
return
78+
end
79+
80+
if data.inAir then
81+
data.inAir = nil
82+
data.groundTicks = 1
83+
return
84+
end
85+
86+
if not data.groundTicks then
87+
return
88+
end
89+
90+
data.groundTicks = data.groundTicks + 1
91+
if data.groundTicks > MAX_GROUND_TICKS then
92+
clearBhopState(id)
93+
end
6294
return
6395
end
6496

97+
-- Airborne
6598
local data = playerData[id]
99+
if data and data.inAir then
100+
return
101+
end
102+
103+
local groundTicks = (data and data.groundTicks) or 0
66104
if not data then
67105
data = {
68-
wasOnGround = false,
69-
groundTicks = 0,
70106
consecutivePerfects = 0,
71107
lastJumpTime = nil,
72108
}
@@ -78,44 +114,37 @@ function Bhop.ProcessPlayer(playerState)
78114
data.consecutivePerfects = 0
79115
end
80116

81-
if onGround then
82-
data.groundTicks = data.groundTicks + 1
83-
data.wasOnGround = true
84-
else
85-
if data.wasOnGround then
86-
data.lastJumpTime = now
87-
if data.groundTicks >= 0 and data.groundTicks <= MAX_GROUND_TICKS then
88-
data.consecutivePerfects = data.consecutivePerfects + 1
89-
90-
if data.consecutivePerfects >= Constants.BHOP_MIN_CONSECUTIVE_SUCCESS then
91-
local lastEvidence = evidenceCooldowns[id] or 0
92-
if (now - lastEvidence) >= BHOP_EVIDENCE_COOLDOWN_S then
93-
Evidence.AddEvidence(id, "bhop", BHOP_EVIDENCE_WEIGHT)
94-
evidenceCooldowns[id] = now
95-
if Common.IsDebugEnabled() then
96-
print(string.format(
97-
"[Bhop] %s perfect jump #%d (ground_ticks=%d) evidence +%.1f",
98-
id, data.consecutivePerfects, data.groundTicks, BHOP_EVIDENCE_WEIGHT))
99-
end
100-
end
117+
data.lastJumpTime = now
118+
if groundTicks >= 0 and groundTicks <= MAX_GROUND_TICKS then
119+
data.consecutivePerfects = data.consecutivePerfects + 1
120+
121+
if data.consecutivePerfects >= Constants.BHOP_MIN_CONSECUTIVE_SUCCESS then
122+
local lastEvidence = evidenceCooldowns[id] or 0
123+
if (now - lastEvidence) >= BHOP_EVIDENCE_COOLDOWN_S then
124+
Evidence.AddEvidence(id, "bhop", BHOP_EVIDENCE_WEIGHT)
125+
evidenceCooldowns[id] = now
126+
if Common.IsDebugEnabled() then
127+
print(string.format(
128+
"[Bhop] %s perfect jump #%d (ground_ticks=%d) evidence +%.1f",
129+
id, data.consecutivePerfects, groundTicks, BHOP_EVIDENCE_WEIGHT))
101130
end
102-
else
103-
data.consecutivePerfects = 0
104131
end
105-
106-
data.wasOnGround = false
107-
data.groundTicks = 0
108132
end
133+
else
134+
data.consecutivePerfects = 0
109135
end
136+
137+
data.groundTicks = nil
138+
data.inAir = true
110139
end
111140

112141
Events.Subscribe("OnPlayerDisconnect", function(id)
113-
playerData[id] = nil
142+
clearBhopState(id)
114143
evidenceCooldowns[id] = nil
115144
end)
116145

117146
Events.Subscribe("OnPlayerRemoved", function(id)
118-
playerData[id] = nil
147+
clearBhopState(id)
119148
evidenceCooldowns[id] = nil
120149
end)
121150

Cheater_Detection/detectors/duck_speed.lua

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ local DUCK_CONFIRM_SECONDS = 3.0
2020
local DUCK_SPEED_EVIDENCE_WEIGHT = 15.0
2121
local DUCK_SPEED_EVIDENCE_COOLDOWN_S = 10.0
2222
local DUCK_SPEED_EVIDENCE_CAP = Evidence.GetMethodScoreCap("duck_speed")
23+
local FL_DUCKING = 2
2324

2425
local tickCounters = {}
2526
local evidenceCooldowns = {}
@@ -47,6 +48,17 @@ function DuckSpeed.HasWork(playerState)
4748
if not pdata or pdata.isDormant or not pdata.isAlive then
4849
return false
4950
end
51+
if not pdata.onGround then
52+
return false
53+
end
54+
local flags = pdata.flags
55+
if not flags or (flags & FL_DUCKING) == 0 then
56+
return false
57+
end
58+
local viewOffset = pdata.viewOffset
59+
if not viewOffset or math.floor(viewOffset.z) ~= FULLY_CROUCHED_VIEW_OFFSET_Z then
60+
return false
61+
end
5062
if Evidence.GetMethodWeight(playerState.id, "duck_speed") >= DUCK_SPEED_EVIDENCE_CAP then
5163
return Common.IsLogCategoryEnabled("All")
5264
end
@@ -65,12 +77,8 @@ function DuckSpeed.ProcessPlayer(playerState)
6577
local pdata = playerState.pdata
6678
local id = playerState.id
6779

68-
local onGround = pdata.onGround
69-
local flags = pdata.flags
7080
local velocity = pdata.velocity
71-
local viewOffset = pdata.viewOffset
72-
73-
if onGround == nil or flags == nil or velocity == nil then
81+
if velocity == nil then
7482
return
7583
end
7684

@@ -91,41 +99,32 @@ function DuckSpeed.ProcessPlayer(playerState)
9199
tickCounters[id] = 0
92100
end
93101

94-
local ducking = (flags & 2) ~= 0
95-
96-
local viewOffsetZ = viewOffset and viewOffset.z or 0
97-
local isFullyCrouched = (math.floor(viewOffsetZ) == FULLY_CROUCHED_VIEW_OFFSET_Z)
98-
99-
if onGround and ducking and isFullyCrouched then
100-
local ent = PlayerData.GetEntity(pdata)
101-
if not ent then
102-
return
103-
end
102+
local ent = PlayerData.GetEntity(pdata)
103+
if not ent then
104+
return
105+
end
104106

105-
local maxSpeed = ent:GetPropFloat("m_flMaxspeed")
106-
local currentSpeed = velocity:Length()
107-
108-
if currentSpeed >= (maxSpeed * DUCK_SPEED_RATIO_MIN) then
109-
tickCounters[id] = tickCounters[id] + 1
110-
111-
if tickCounters[id] >= getConfirmTicks() then
112-
local beforeWeight = Evidence.GetMethodWeight(id, "duck_speed")
113-
Evidence.AddEvidence(id, "duck_speed", DUCK_SPEED_EVIDENCE_WEIGHT)
114-
tickCounters[id] = 0
115-
evidenceCooldowns[id] = now
116-
if Common.IsDebugEnabled() then
117-
print(string.format(
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")))
122-
end
107+
local maxSpeed = ent:GetPropFloat("m_flMaxspeed")
108+
local currentSpeed = velocity:Length()
109+
110+
if currentSpeed >= (maxSpeed * DUCK_SPEED_RATIO_MIN) then
111+
tickCounters[id] = tickCounters[id] + 1
112+
113+
if tickCounters[id] >= getConfirmTicks() then
114+
local beforeWeight = Evidence.GetMethodWeight(id, "duck_speed")
115+
Evidence.AddEvidence(id, "duck_speed", DUCK_SPEED_EVIDENCE_WEIGHT)
116+
tickCounters[id] = 0
117+
evidenceCooldowns[id] = now
118+
if Common.IsDebugEnabled() then
119+
print(string.format(
120+
"[DuckSpeed] %s duck speed (evidence +%.1f, total duck_speed=%.1f)",
121+
id,
122+
Evidence.GetMethodWeight(id, "duck_speed") - beforeWeight,
123+
Evidence.GetMethodWeight(id, "duck_speed")))
123124
end
124-
else
125-
tickCounters[id] = math.max(0, tickCounters[id] - 1)
126125
end
127126
else
128-
tickCounters[id] = 0
127+
tickCounters[id] = math.max(0, tickCounters[id] - 1)
129128
end
130129
end
131130

0 commit comments

Comments
 (0)