123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942 |
- -- cheat_detection: A minetest addon that will check/target/ban players for potential abnormal behavior caused commonly by hacked clients
- -- Copyright (C) 2020 Genshin <emperor_genshin@hotmail.com>
- --
- -- This program is free software: you can redistribute it and/or modify
- -- it under the terms of the GNU Affero General Public License as
- -- published by the Free Software Foundation, either version 3 of the
- -- License, or (at your option) any later version.
- --
- -- This program is distributed in the hope that it will be useful,
- -- but WITHOUT ANY WARRANTY; without even the implied warranty of
- -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- -- GNU Affero General Public License for more details.
- --
- -- You should have received a copy of the GNU Affero General Public License
- -- along with this program. If not, see <http://www.gnu.org/licenses/>.
- --TODO: Test further to find additional false positives
- local enable_automod = minetest.settings:get('enable_cheat_detection_automod') or false
- local automod_type = minetest.settings:get('cheat_detection_automod_type') or "ban"
- local automod_reason = minetest.settings:get('cheat_detection_automod_reason') or "Excessive Cheating Attempts"
- local cheat_detection_step = tonumber(minetest.settings:get("cheat_detection_step")) or 0
- local server_step = tonumber(minetest.settings:get("dedicated_server_step")) or 0.1
- local patience_meter = 3
- local cheat_patience_meter = 3
- local node_under_height = 0.7
- local server_host = minetest.settings:get("name") or ""
- local detection_list = {}
- local debug_hud_list = {}
- if enable_automod and type(enable_automod) == "string" then
- if enable_automod == "true" then
- enable_automod = true
- elseif enable_automod == "false" then
- enable_automod = false
- end
- end
- local function get_velocity_as_whole_interger(player, dir)
- local result = nil
- local velocity = player:get_player_velocity()
- local vel_x, vel_z = nil
- if dir == "horizontal" then
- local speed = nil
- vel_x = math.floor(velocity.x)
- vel_z = math.floor(velocity.z)
- if vel_x < 0 and vel_z >= 0 then
- vel_x = math.abs(vel_x)
- elseif vel_z < 0 and vel_x >= 0 then
- vel_z = math.abs(vel_x)
- end
- if vel_x > vel_z then
- speed = math.abs(vel_x)
- elseif vel_z > vel_x then
- speed = math.abs(vel_z)
- elseif vel_x == vel_z then
- speed = math.abs(vel_x or vel_z)
- end
- result = speed
- elseif dir == "vertical" then
- result = velocity.y
- end
- return result
- end
- --Patch for unknown block detection skipping, we really just need to get useful properties only anyway
- local function verify_node(node)
- local def = minetest.registered_nodes[node.name]
- --Is it a undefined block? if so generate some properties for it
- if def == nil then
- def = {walkable = true, drawtype = "normal"}
- end
- return def
- end
- local function add_tracker(player)
- local name = player:get_player_name()
- if not detection_list[name] then
- detection_list[name] = {
- suspicion = "None",
- prev_pos = {x = 0, y = 0, z = 0},
- prev_velocity = {x = 0, y = 0, z = 0},
- strikes = 0,
- patience_cooldown = 2,
- logged_in = true,
- logged_in_cooldown = 4,
- li_cd_full_time = 0,
- automod_triggers = 0,
- instant_punch_time = 0,
- anticheat_callout_time = 0,
- unhold_sneak_time = 0,
- liquid_walk_time = 0,
- flight_time = 0,
- time_resetted = false,
- falling_stops_time = 0,
- node_clipping_time = 0,
- flying = false,
- alert_sent = false,
- punched = false,
- falling = false,
- killaura = false,
- fast_dig = false,
- instant_break = false,
- abnormal_range = false,
- killaura_check = false,
- }
- end
- end
- local function update_tracker_info(player, list)
- local name = player:get_player_name()
- if detection_list[name] then
- detection_list[name] = list
- end
- end
- local function remove_tracker(player)
- local name = player:get_player_name()
- if detection_list[name] then
- detection_list[name] = nil
- end
- end
- local function get_tracker(player)
- local name = player:get_player_name()
- local result = nil
- if detection_list[name] then
- result = detection_list[name]
- end
- return result
- end
- local function cast_ray_under_player(player, range, objects, liquids)
- local pos = player:get_pos()
- objects = objects or false
- liquids = liquids or false
- --Raycast stuff.
- local ray_start = vector.add({x = pos.x, y = pos.y - 1, z = pos.z}, {x=0, y=0, z=0})
- local ray_modif = vector.multiply({x = 0, y = -0.9, z = 0}, range) --point ray down
- local ray_end = vector.add(ray_start, ray_modif)
- local ray = minetest.raycast(ray_start, ray_end, objects, liquids)
- local object = ray:next()
- --Skip player's collision
- if object and object.type == "object" and object.ref == player then
- object = ray:next()
- end
- return object
- end
- local function cast_ray_under_pos(pos, range, objects, liquids)
- objects = objects or false
- liquids = liquids or false
- --Raycast stuff.
- local ray_start = vector.add({x = pos.x, y = pos.y - 0.1, z = pos.z}, {x=0, y=0, z=0})
- local ray_modif = vector.multiply({x = 0, y = -0.9, z = 0}, range) --point ray down
- local ray_end = vector.add(ray_start, ray_modif)
- local ray = minetest.raycast(ray_start, ray_end, objects, liquids)
- local object = ray:next()
- --
- return object
- end
- --check if player has a obstacle underneath him (Returns a boolean)
- local function check_obstacle_found_under(player, range)
- local result = false
- local object = cast_ray_under_player(player, range, true, true)
- if object and object.type then
- result = true
- end
- return result
- end
- --check if player has a obstacle underneath him and get their properties (Returns a table of properties, if failed returns nil)
- local function get_obstacle_found_under(player, range)
- local result = nil
- local object = cast_ray_under_player(player, range, true, true)
- if object and object.type then
- if object.type == "node" then
- local node = minetest.get_node(object.under)
- --We need to make sure this raycast does not grab air as it's final target
- if node then
- local def = verify_node(node)
- result = {name = node.name, type = "node", def = def}
- end
- elseif object.type == "object" and object.ref and not(object.ref:is_player() or object.ref == player) then
- local entity = object.ref:get_luaentity()
- result = {name = entity.name, type = "entity", def = entity}
- end
- end
- return result
- end
- --check if position has a node underneath it and get their properties (Returns a table of properties, if failed returns nil)
- local function get_node_under_ray(pos, range)
- local result = nil
- local object = cast_ray_under_pos(pos, range, false, true)
- if object and object.type and object.type == "node" then
- local node = minetest.get_node(object.under)
- --We need to make sure this raycast does not grab air as it's final target
- if node then
- local def = verify_node(node)
- result = def
- end
- end
- return result
- end
- --needed for flight check and jesus walk checks to prevent false positives by entity collision
- local function check_if_entity_under(pos)
- local entities = minetest.get_objects_inside_radius({x = pos.x, y = pos.y, z = pos.z}, 1)
- local result = false
- --look for physical objects only (TODO: convert method to raycast)
- for _,entity in pairs(entities) do
- if not entity:is_player() and entity:get_luaentity().physical == true then
- result = true
- break
- end
- end
- return result
- end
- local function check_player_is_inside_nodes(player)
- local pos = player:get_pos()
- local node_top = minetest.get_node({x = pos.x, y = pos.y + 1, z = pos.z})
- local node_bottom = minetest.get_node(pos)
- local result = false
- if node_top and node_bottom then
- node_top = minetest.registered_nodes[node_top.name]
- node_bottom = minetest.registered_nodes[node_bottom.name]
- if node_top and node_top.walkable and node_bottom and node_bottom.walkable then
- result = true
- end
- end
- return result
- end
- local function check_player_is_swimming(player)
- local pos = player:get_pos()
- local node_top = minetest.get_node({x = pos.x, y = pos.y + 1, z = pos.z})
- local node_bottom = minetest.get_node(pos)
- local result = false
- if node_top and node_bottom then
- node_top = minetest.registered_nodes[node_top.name]
- node_bottom = minetest.registered_nodes[node_bottom.name]
- if type(node_top) == "table"
- and type(node_bottom) == "table"
- and (node_top.drawtype == "liquid"
- or node_top.drawtype == "flowingliquid"
- or node_bottom.drawtype == "liquid"
- or node_bottom.drawtype == "flowingliquid") then
- result = true
- end
- end
- return result
- end
- local function is_solid_node_under(pos, max_height)
- local result = false
- local y_steps = 0
- local found = false
- while max_height > y_steps do
- local node = minetest.get_node({x = pos.x, y = pos.y - y_steps, z = pos.z})
- if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].drawtype ~= "airlike" and minetest.registered_nodes[node.name].walkable == true then
- found = true
- result = true
- elseif not minetest.registered_nodes[node.name] then --unknown block
- found = true
- result = true
- end
- if found then
- print(node.name)
- break
- end
- y_steps = y_steps + 1
- end
- return result
- end
- --Check surroundings for nodes in a 3x3 block order (Returns specified node property value if successful, if not then it returns nil)
- local function check_surrounding_for_nodes(height, pos)
- local result = false
- local scan_tries = 8
- --TODO: false positive - unable to grab slabs, fences and banners with raycast, make standard get_node() failsafe [Sneak Key] *facepalm...
- --Only scan for nearby nodes by 3x3 blocks
- while scan_tries > 0 do
- local new_pos = nil
- local node = nil
- if scan_tries == 8 then
- new_pos = {x = pos.x, y = pos.y + height, z = pos.z - 1}
- node = is_solid_node_under(new_pos, 4)
- elseif scan_tries == 7 then
- new_pos = {x = pos.x - 1, y = pos.y + height, z = pos.z - 1}
- node = is_solid_node_under(new_pos, 4)
- elseif scan_tries == 6 then
- new_pos = {x = pos.x + 1, y = pos.y + height, z = pos.z + 1}
- node = is_solid_node_under(new_pos, 4)
- elseif scan_tries == 5 then
- new_pos = {x = pos.x - 1, y = pos.y + height, z = pos.z}
- node = is_solid_node_under(new_pos, 4)
- elseif scan_tries == 4 then
- new_pos = {x = pos.x - 1, y = pos.y + height, z = pos.z + 1}
- node = is_solid_node_under(new_pos, 4)
- elseif scan_tries == 3 then
- new_pos = {x = pos.x + 1, y = pos.y + height, z = pos.z - 1}
- node = is_solid_node_under(new_pos, 4)
- elseif scan_tries == 2 then
- new_pos = {x = pos.x + 1, y = pos.y + height, z = pos.z}
- node = is_solid_node_under(new_pos, 4)
- elseif scan_tries == 1 then
- new_pos = {x = pos.x, y = pos.y + height, z = pos.z + 1}
- node = is_solid_node_under(new_pos, 4)
- end
- print(tostring(scan_tries)..") "..tostring(node))
- if node == true then
- result = node
- break
- end
- scan_tries = scan_tries - 1
- end
- return result
- end
- --Alert staff if goon is pulling hacks out of his own ass
- local function send_alert_to_serverstaff(suspect, suspicion)
- local players = minetest.get_connected_players()
- for _,player in pairs(players) do
- local name = player:get_player_name()
- local info = get_tracker(player)
- local is_staff = minetest.check_player_privs(name, {ban=true})
- --Do not spam these alerts more than once per accusation since staff can get annoyed by accusation spam
- if is_staff == true then
- minetest.chat_send_player(name, minetest.colorize("#ffbd14" ,"*** "..os.date("%X")..":[CHEAT DETECTION]: Player ")..minetest.colorize("#FFFFFF", tostring(suspect))..minetest.colorize("#ffbd14" ," may be performing ")..minetest.colorize("#FF0004", tostring(suspicion))..minetest.colorize("#ffbd14" ," hacks!"))
- end
- end
- end
- local function check_if_forced_flying(player, info, pos, velocity, avg_rtt)
- local result = false
- --Skip flight check if punched or logged in
- if info.logged_in == true then
- return result
- elseif info.punched == true then
- return result
- end
- local name = player:get_player_name()
- local can_fly = minetest.check_player_privs(name, {fly=true})
- local min_speed = tonumber(minetest.settings:get("movement_speed_fast")) or 20
- local min_jump = tonumber(minetest.settings:get("movement_speed_jump")) or 6.5
- local speed_mod = tonumber(player:get_physics_override().speed)
- local node_under = is_solid_node_under(pos, 1)
- local sneak_hold = player:get_player_control().sneak or false
- local object_under = check_obstacle_found_under(player, 1)
- local suspicious = false
- local delay = tonumber(patience_meter + avg_rtt)
- local node_type = ""
- min_speed = math.floor(speed_mod * min_speed)
-
- --Reset sneak unhold time if player is sneak glitching
- if sneak_hold == true then
- info.unhold_sneak_time = 0
- end
- -- print(node_under, object_under, sneak_hold)
- if info.flying == true then
- suspicious = true
- end
- --check if they are standing still while hovering in the air
- if node_under == false and object_under == false and can_fly == false and pos.y == info.prev_pos.y and velocity.y == 0 and sneak_hold == false and info.flying == false then
- local was_falling = info.falling or false
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." triggered the Hover Check.")
- object_under = check_if_entity_under(pos)
- --Prevent/skip false positive to trigger by unloaded block lag when falling too fast or when a object is underneath or if he/she just had loggen in to spare them from a aggressive detection
- if was_falling == true or object_under == true then
- info.flight_time = 0
- info.falling_stops_time = 0
- if was_falling == true then
- info.falling = false
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." was falling down but was halted by unloaded blocks, No suspicious activity found.")
- elseif object_under == true then
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." is standing on a entity obstacle, No suspicious activity found.")
- end
- return result
- end
- local nodes_around = check_surrounding_for_nodes(2, pos)
- if nodes_around == false then
- info.unhold_sneak_time = info.unhold_sneak_time + 1
- end
- --print("Sneak Unhold Time: "..tostring(info.unhold_sneak_time))
- --Get triggered if the player has been caught constantly hovering up in the air
- if info.unhold_sneak_time >= delay then
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is hovering, Server has marked this as suspicious activity!")
- suspicious = true
- end
- --Check if player is indeed flying
- elseif node_under == false and object_under == false and can_fly == false and velocity.y > min_jump and info.flying == false then
- if info.flying == true then
- return true
- end
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." triggered the Flight Check.")
- --Reset Falling Stops Time due to Flight check
- info.falling_stops_time = 0
- info.unhold_sneak_time = 0
- info.falling = false
- --Check for vertical velocity change to determine if the player is actually flying
- local new_velocity = player:get_player_velocity()
- --Get triggered if the player has been constantly climbing up for far too long at a very steady pace
- if info.flight_time >= delay then
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is continuously increasing vertical velocity too many times. Server has marked this as suspicious activity!")
- suspicious = true
- --If bastard is constantly acsending steadily at max speed, then he's flying like a little twat. add flight time
- elseif new_velocity.y == min_speed then
- info.flight_time = info.flight_time + 1
- --If a player suddently begins dropping vertical velocity, then they might be falling
- elseif new_velocity.y < info.prev_velocity.y then
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." seem to be dropping vertical velocity due to falling. No suspicious activity found.")
- --If a player is not flying, they can't be able to keep the same ammount of previous vertical velocity or constantly increasing their vertical velocity, if so they are really flying high
- elseif new_velocity.y >= info.prev_velocity.y and new_velocity.y < min_speed then
- info.prev_velocity = new_velocity
- info.flight_time = info.flight_time + 1
- end
- --print("Flight Time: "..tostring(info.flight_time))
- --Check if player is falling, or flying (false positives found)
- elseif node_under == false and object_under == false and can_fly == false and velocity.y < -min_jump and info.flying == false then
- if info.flying == true then
- return true
- end
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." triggered the Fall Check.")
- --Reset Flight Time due to Falling check
- info.flight_time = 0
- info.unhold_sneak_time = 0
- --Check for vertical velocity change to determine if the player is actually flying
- local new_velocity = player:get_player_velocity()
- --print(new_velocity.y, info.prev_velocity.y)
- --If still falling, then stop this...
- if info.falling == true then
- info.prev_velocity = new_velocity
- info.falling_stops_time = 0
- return result
- --Get triggered if the player has been constantly stopping from falling for far too long at a very steady pace
- elseif info.falling_stops_time >= delay then
- info.prev_velocity = new_velocity
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is continuously stopping from falling down too many times. Server has marked this as suspicious activity!")
- suspicious = true
- --If bastard is constantly decsending steadily at max speed, then he's flying like a little twat. add falling stop time
- elseif new_velocity.y == -min_speed then
- info.prev_velocity = new_velocity
- info.falling_stops_time = info.falling_stops_time + 1
-
- --if falling down, reset timer
- elseif new_velocity.y < info.prev_velocity.y and new_velocity.y > -min_speed then
- info.prev_velocity = new_velocity
- info.falling_stops_time = 0
- --If a player suddently begins dropping vertical velocity above max speed, then they are indeed falling
- elseif new_velocity.y < -min_speed then
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." is confirmed falling. No suspicious activity found.")
- info.prev_velocity = new_velocity
- info.falling = true
- is_solid_node
- --If a player is falling, they can't be able to suddenly increase vertical velocity or make their velocity stay the same
- elseif new_velocity.y >= info.prev_velocity.y then
- minetest.log("action", "[CHEAT DETECTION]: Player "..name.." suddenly stopped falling (Could be due to unloaded blocks or lag), verifying behavior...")
- info.prev_velocity = new_velocity
- info.falling_stops_time = info.falling_stops_time + 1
- end
- --Get triggered if the player has been constantly climbing up for far too long
- if info.falling_stops_time >= delay then
- suspicious = true
- end
- --print("Fall Time: "..tostring(info.falling_stops_time))
- --Just a normal player doing normal things, reset timers
- elseif node_under == true or object_under == true then
- suspicious = false
- info.flying = false
- info.falling = false
- info.unhold_sneak_time = 0
- info.flight_time = 0
- info.falling_stops_time = 0
- end
- if suspicious == true then
- info.flying = true
- result = true
- end
- return result
- end
- local function check_if_forced_noclipping(player, info, velocity, avg_rtt)
- local result = false
- --Skip Noclip check if punched
- if info.punched == true then
- return result
- end
- local name = player:get_player_name()
- local delay = tonumber(patience_meter + avg_rtt)
- local can_noclip = minetest.check_player_privs(name, {noclip=true})
- local inside_nodes = check_player_is_inside_nodes(player)
- --if someone is inside a solid node then they shouldn't be moving at all, if they are then it's clearly due to noclipping
- if inside_nodes == true and can_noclip == false and (velocity.x > 5 or velocity.x < -5 or velocity.z > 5 or velocity.z < -5) then
- info.node_clipping_time = info.node_clipping_time + 1
- if info.node_clipping_time > delay then
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is clipping through solid nodes while moving without noclip privileges. Server has marked this as suspicious activity!")
- info.strikes = 3
- result = true
- end
- else
- info.node_clipping_time = 0
- end
- return result
- end
- local function check_if_forced_fast(player, info)
- local result = false
- --Skip Jesus Walk check if punched
- if info.punched == true then
- return result
- end
- local aux_pressed = player:get_player_control().aux1
- --if player is not pressing sprint key, skip this check
- if aux_pressed == false then
- return result
- end
- local name = player:get_player_name()
- local current_speed = get_velocity_as_whole_interger(player, "horizontal")
- local min_speed = tonumber(minetest.settings:get("movement_speed_fast")) or 20
- local detection_fast_speed = nil
- local can_fast = minetest.check_player_privs(name, {fast=true})
- local speed_mod = tonumber(player:get_physics_override().speed)
- local f = math.floor
- --This is needed to determine if user is speeding, subtract 1 for fast speed accuracy
- min_speed = math.floor(speed_mod * min_speed)
- detection_fast_speed = math.floor(min_speed - 1)
- if can_fast == false and (current_speed == min_speed or current_speed == detection_fast_speed) then
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.."\'s speed went past the server\'s max speed without fast privs too many times. Server has marked this as suspicious activity!")
- result = true
- end
- return result
- end
- local function check_if_jesus_walking(player, info, pos, velocity, avg_rtt)
- local result = false
- --Skip Jesus Walk check if punched
- if info.punched == true then
- return result
- end
- local name = player:get_player_name()
- local obstacle_under = get_obstacle_found_under(player, 1)
- local node_under = "not found"
- local swimming = check_player_is_swimming(player)
- local sneak_hold = player:get_player_control().sneak or false
- local delay = tonumber(patience_meter + avg_rtt)
- if obstacle_under and obstacle_under.type == "node" and obstacle_under.def.drawtype then
- node_under = obstacle_under.def.drawtype
- end
- --If someone is able to stand still on a liquid type node, then they are clearly walking on water
- if swimming == false and (node_under == "liquid" or node_under == "flowingliquid") and pos.y == info.prev_pos.y and velocity.y == 0 and sneak_hold == false then
- local object_under = check_if_entity_under(pos)
- if object_under == false then
- info.node_clipping_time = info.node_clipping_time + 1
- info.liquid_walk_time = info.liquid_walk_time + 1
- print("Liquid Walk Time: "..tostring(info.liquid_walk_time))
- end
- else
- info.liquid_walk_time = 0
- end
- --Get triggered if the player has been constantly walking on water for far too long
- if info.liquid_walk_time >= delay then
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is litteraly standing on water, Server has marked this as suspicious activity!")
- info.strikes = 3
- result = true
- end
- return result
- end
- local function verify_suspicious_behavior(info, suspicion, avg_rtt)
- local timer_step = tonumber(cheat_detection_step + avg_rtt)
- minetest.after(timer_step, function()
- info.strikes = info.strikes + 1
- end)
- --Don't go past 3 strikes
- if info.strikes >= 3 then
- info.suspicion = suspicion
- info.strikes = 3
- info.patience_cooldown = 2
- end
- end
- --Enable Server Anti-Cheat System if Player Manager is Present, keep an eye out for suspicious activity
- local function handle_cheat_detection()
- local players = minetest.get_connected_players()
- for _,player in pairs(players) do
- local pname = player:get_player_name()
- local pos = player:get_pos()
- local velocity = player:get_player_velocity()
- local is_superuser = minetest.check_player_privs(pname, {server=true})
- local pinfo = minetest.get_player_information(pname) --this is important
- local info = get_tracker(player)
- local smite = false
- if pinfo and info and pname ~= server_host and is_superuser == false then
- --If detection step is too fast, slow down the cooldown timer so some detection algorythms are less aggressive when a player reconnects to the server after jumping
- if info.logged_in == true and info.logged_in_cooldown > 0 and cheat_detection_step < 0.3 and server_step <= 0.1 then
- info.li_cd_full_time = info.li_cd_full_time + 1
- if info.li_cd_full_time > 15 then
- info.logged_in_cooldown = info.logged_in_cooldown - 1
- info.li_cd_full_time = 0
- end
- if info.logged_in_cooldown < 1 then
- info.logged_in = false
- info.logged_in_cooldown = nil
- end
- --If detection step is at a balanced rate, then normally count down without any further delay
- elseif info.logged_in == true and info.logged_in_cooldown > 0 and (cheat_detection_step >= 0.3 or server_step >= 0.1) then
- info.logged_in_cooldown = info.logged_in_cooldown - 1
- if info.logged_in_cooldown < 1 then
- info.logged_in = false
- info.logged_in_cooldown = nil
- end
- end
- --Scan players every single average round trip time for accuracy
- minetest.after(pinfo.avg_rtt, function()
- info.time_resetted = false
- --Only do debug for dummy test hacker client to see what's up whith him
- if pname == "haxor" then
- update_debug_hud(player, false)
- end
- local is_jesus_walking = check_if_jesus_walking(player, info, pos, velocity, pinfo.avg_rtt)
- local is_force_noclipping = check_if_forced_noclipping(player, info, velocity, pinfo.avg_rtt)
- local is_force_fast = check_if_forced_fast(player, info)
- local is_force_flying = check_if_forced_flying(player, info, pos, velocity, pinfo.avg_rtt)
- --Hmm, I sense suspicious activity in this sector... [Killaura]
- if info.killaura == true then
- verify_suspicious_behavior(info, "Killaura", pinfo.avg_rtt)
- info.killaura_check = false
- info.killaura = false
- --Hmm, I sense suspicious activity in this sector... [Unlimited Range]
- elseif info.abnormal_range == true then
- verify_suspicious_behavior(info, "Unlimited Range", pinfo.avg_rtt)
- info.abnormal_range = false
- --Hmm, I sense suspicious activity in this sector... [Instant Break]
- elseif info.instant_break == true then
- verify_suspicious_behavior(info, "Instant Node Break", pinfo.avg_rtt)
- info.instant_break = false
- --Hmm, I sense suspicious activity in this sector... [Fast Dig]
- elseif info.fast_dig == true then
- verify_suspicious_behavior(info, "Fast Dig", pinfo.avg_rtt)
- info.fast_dig = false
- --Hmm, I sense suspicious activity in this sector... [Walk on Water Hacks]
- elseif is_jesus_walking == true then
- verify_suspicious_behavior(info, "Jesus Walk", pinfo.avg_rtt)
- --Hmm, I sense suspicious activity in this sector... [Noclip Hacks]
- elseif is_force_noclipping == true then
- verify_suspicious_behavior(info, "Forced Noclip", pinfo.avg_rtt)
- --Hmm, I sense suspicious activity in this sector... [Fast Hacks]
- elseif is_force_fast == true then
- verify_suspicious_behavior(info, "Forced Fast", pinfo.avg_rtt)
- --Hmm, I sense suspicious activity in this sector... [Fly Hacks]
- elseif is_force_flying == true then
- verify_suspicious_behavior(info, "Forced Fly", pinfo.avg_rtt)
- --So far so good, nothing to see here (Reset timers and strikes)
- else
- info.patience_cooldown = info.patience_cooldown - 1
- if info.patience_cooldown < 1 then
- info.time_resetted = true
- info.automod_triggers = 0
- info.patience_cooldown = 2
- end
- end
- --Send Warning after 3 strikes, then reset. Following up with patience meter to drop
- if info.strikes == 3 and info.suspicion ~= "None" then
- send_alert_to_serverstaff(pname, info.suspicion)
- if info.alert_sent == false then
- minetest.log("warning", "[CHEAT DETECTION]: Player "..pname.." have been flagged by the Server for possibly using a Hacked Client!")
- minetest.chat_send_player(pname, minetest.colorize("#ffbd14" ,"*** "..os.date("%X")..":[CHEAT DETECTION]: You have been flagged by the Server for possibly using a Hacked Client. Our server staff have been alerted!"))
- info.alert_sent = true
- end
- if enable_automod == true then
- local delay = tonumber(patience_meter + pinfo.avg_rtt)
- info.automod_triggers = info.automod_triggers + 1
- if info.automod_triggers >= delay then
- smite = true
- end
- end
- info.strikes = 0
- elseif info.strikes == 3 and info.suspicion == "None" then
- info.strikes = 0
- end
- --I ran out of patience, please for the love of god Let me BAN this sneaky little twat NOW!!!
- if smite and enable_automod == true then
- info.automod_triggers = 0
- if automod_type == "kick" then
- minetest.log("action", "[CHEAT DETECTION]: Server has kicked "..pname.." for performing continuous abnormal behaviors while in-game.")
- minetest.kick_player(pname, "Cheat Detection: "..automod_reason)
- elseif automod_type == "ban" then
-
- minetest.log("action", "[CHEAT DETECTION]: Server has banned "..pname.." for performing continuous abnormal behaviors while in-game.")
- minetest.ban_player(pname)
-
- end
- end
- info.punched = false
- info.prev_velocity = velocity
- info.prev_pos = pos
- update_tracker_info(player, info)
- end)
- end
- end
- minetest.after(cheat_detection_step, function()
- handle_cheat_detection()
- end)
- end
- minetest.register_on_mods_loaded(function()
- handle_cheat_detection()
- end)
- minetest.register_on_leaveplayer(function(player)
- remove_tracker(player)
- update_debug_hud(player, true)
- end)
- minetest.register_on_joinplayer(function(player)
- --Add the dang tracker onto the specified player
- add_tracker(player)
- end)
- --Additional Anti-Hackerman stuff
- minetest.register_on_cheat(function(player, cheat)
- local info = get_tracker(player)
- --Skip shenanigain check if player is punched, this is for knockback exceptions
- if info.punched == true or info.killaura_check == true then
- return
- end
- local name = player:get_player_name()
- local pinfo = minetest.get_player_information(name)
- local delay = tonumber(cheat_patience_meter + pinfo.avg_rtt)
- local accusation = nil
- if cheat.type == "interacted_too_far" then
- info.anticheat_callout_time = info.anticheat_callout_time + 1
- accusation = "unlimitedrange"
- elseif cheat.type == "dug_unbreakable" then
- info.anticheat_callout_time = info.anticheat_callout_time + 1
- accusation = "instantbreak"
- elseif cheat.type == "dug_too_fast" then
- info.anticheat_callout_time = info.anticheat_callout_time + 1
- accusation = "fastdig"
- end
- --Hackers normally do things very quickly
- if accusation and info.anticheat_callout_time > delay then
- if accusation == "instantbreak" then
- info.instant_break = true
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is trying to instantly break unbreakable nodes too many times. Server has marked this as suspicious activity!")
- elseif accusation == "fastdig" then
- info.fast_dig = true
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is trying to instantly dig nodes too many times. Server has marked this as suspicious activity!")
- elseif accusation == "unlimitedrange" then
- info.abnormal_range = true
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is trying to do long range interactions too many times. Server has marked this as suspicious activity!")
- end
- info.anticheat_callout_time = 0
- end
- end)
- minetest.register_on_punchplayer(function(player, hitter, punchtime)
- local info = get_tracker(player)
- local name = nil
- local info2 = nil
- local pinfo = nil
- local delay = nil
- --Knockback Exceptions
- if info then
- info.punched = true
- end
- if hitter:is_player() then
- name = hitter:get_player_name()
- info2 = get_tracker(hitter)
- pinfo = minetest.get_player_information(name)
- delay = tonumber(patience_meter + pinfo.avg_rtt - 1)
- end
- --killaura detection, there is absolutely no flipping way any normal human being can land a 0 second punch repeatedly (No Mercy for these scrubs)
- if info2 and punchtime <= 0 then
- info2.killaura_check = true
- info2.instant_punch_time = info2.instant_punch_time + 1
- --Confirm killaura behavior if player instantly punched a player too many times
- if info2.instant_punch_time > delay then
- info2.killaura = true
- minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is instantly punching players too many times. Server has marked this as suspicious activity!")
- end
- elseif info2 and punchtime > 0 then
- info2.instant_punch_time = 0
- end
- end)
|