MineX server Posted June 22, 2025 Share Posted June 22, 2025 Hello, I'm developing an anti-cheat system in MTA:SA and I'm trying to detect players who block or cancel the `onClientPlayerDamage` event on their side, which prevents the server from detecting damage via `onPlayerDamage`. I know that if the client cancels `onClientPlayerDamage` using `cancelEvent()`, the server-side `onPlayerDamage` event will not be triggered. Resulting in godmode and players not dying So my question is: - Is there a reliable way to detect if the client-side damage event was cancelled? - Does MTA have an anti cheat against this type of cheating ? -Are there other ways for cheating to cancel damage taken and not die? Any suggestions or ideas to detect and prevent this kind of abuse would be appreciated! Thanks in advance. Link to comment
CastiaL Posted July 3, 2025 Share Posted July 3, 2025 function stopUnknownDamage(attacker, damagetype, bodypart) if (damagetype == 55) then --if the no known information about this damage type cancelEvent() --cancel the event end end addEventHandler("onClientPlayerDamage", localPlayer, stopUnknownDamage) Link to comment
Moderators IIYAMA Posted July 3, 2025 Moderators Share Posted July 3, 2025 On 22/06/2025 at 12:58, MineX server said: - Is there a reliable way to detect if the client-side damage event was cancelled? You might be able to detect it using: https://wiki.multitheftauto.com/wiki/WasEventCancelled Though I do not know if this functions works correctly for native events. Something you have to test yourself. Make sure to set the addEventHandler to low priority. Link to comment
CastiaL Posted July 4, 2025 Share Posted July 4, 2025 addEventHandler("onClientPlayerDamage", localPlayer, function() if wasEventCancelled() then --triggerServerEvent("cancelDetected", localPlayer) end end, false, -999) -- low priority Link to comment
Ze9 Posted October 2, 2025 Share Posted October 2, 2025 Men the problem is that you dont get that you should never trust the client use the onPlayerDamage in server side In this case and even if he cancel the event client side he still dies server side Only use the onclientDamage if you want to build a ui or some text only for visuals Don't ever make a system relies to the trust of the client side Hope you understand what i mean Link to comment
ZeusDev Posted December 22, 2025 Share Posted December 22, 2025 On 02/10/2025 at 20:42, Ze9 said: Men the problem is that you dont get that you should never trust the client use the onPlayerDamage in server side In this case and even if he cancel the event client side he still dies server side Only use the onclientDamage if you want to build a ui or some text only for visuals Don't ever make a system relies to the trust of the client side Hope you understand what i mean Unfortunately that's not how MTA's damage system works. The official Wiki explicitly states: Canceling this event has no effect. Cancel the client-side event onClientPlayerDamage instead. So onPlayerDamage server-side is not authoritative at all. If a cheater cancels onClientPlayerDamage on their client, the server never receives onPlayerDamage because the damage was prevented client-side before synchronization even happens. The player becomes invincible and your server sees absolutely nothing. The phrase "never trust the client" is correct, but in this case MTA's architecture forces the client to be trusted for damage events. That's the core problem here. A Real Approach: Server-Side Health Validation Since you can't rely on damage events arriving, you need to validate expected damage against actual health changes using data you CAN trust server-side: local playerHealthCache = {} local HEALTH_DISCREPANCY_THRESHOLD = 50 addEventHandler("onPlayerSpawn", root, function() playerHealthCache[source] = getElementHealth(source) end) addEventHandler("onPlayerWeaponFire", root, function(weapon, _, _, _, _, _, target) if not target or not isElement(target) or getElementType(target) ~= "player" then return end local expectedDamage = getWeaponDamage(weapon) local currentHealth = playerHealthCache[target] or getElementHealth(target) local expectedHealth = math.max(0, currentHealth - expectedDamage) setTimer(function() if not isElement(target) then return end local actualHealth = getElementHealth(target) local discrepancy = actualHealth - expectedHealth if discrepancy > HEALTH_DISCREPANCY_THRESHOLD then flagSuspiciousPlayer(target, "GODMODE_SUSPECTED", discrepancy) end playerHealthCache[target] = actualHealth end, 150, 1) end) function getWeaponDamage(weaponID) local damageTable = { [22] = 8.25, [23] = 13.2, [24] = 46.2, [25] = 49.5, [26] = 49.5, [27] = 39.6, [28] = 6.6, [29] = 8.25, [30] = 9.9, [31] = 9.9, [32] = 46.2, [33] = 75, [34] = 75, [38] = 46.2 } return damageTable[weaponID] or 25 end function flagSuspiciousPlayer(player, reason, data) local serial = getPlayerSerial(player) outputDebugString(("[AC] %s flagged: %s (data: %s)"):format(getPlayerName(player), reason, tostring(data))) end addEventHandler("onPlayerQuit", root, function() playerHealthCache[source] = nil end) The idea is simple: when someone fires a weapon at a player, you know the expected damage. Then you check if their health actually dropped by that amount. If they keep taking hits but health never goes down, something's wrong. Also worth noting that wasEventCancelled() on client won't help for anti-cheat since the cheater controls that environment entirely. They can just patch out the detection or spoof the result. Link to comment
Moderators IIYAMA Posted December 31, 2025 Moderators Share Posted December 31, 2025 On 22/12/2025 at 10:35, ZeusDev said: The idea is simple: when someone fires a weapon at a player, you know the expected damage. Then you check if their health actually dropped by that amount. If they keep taking hits but health never goes down, something's wrong. Just to verify, does this actually work? Because 'target' is afaik set by the player that fired the weapon and not by the player that got hit. And therefore there are a lot false positives, unless you let remote players decide the damage done to the localPlayer (which is not handy). addEventHandler("onPlayerWeaponFire", root, function(weapon, _, _, _, _, _, target) Link to comment
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now