init.lua 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. -- cheat_detection: A minetest addon that will check/target/ban players for potential abnormal behavior caused commonly by hacked clients
  2. -- Copyright (C) 2020 Genshin <emperor_genshin@hotmail.com>
  3. --
  4. -- This program is free software: you can redistribute it and/or modify
  5. -- it under the terms of the GNU Affero General Public License as
  6. -- published by the Free Software Foundation, either version 3 of the
  7. -- License, or (at your option) any later version.
  8. --
  9. -- This program is distributed in the hope that it will be useful,
  10. -- but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. -- GNU Affero General Public License for more details.
  13. --
  14. -- You should have received a copy of the GNU Affero General Public License
  15. -- along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. --TODO: Test further to find additional false positives
  17. local enable_automod = minetest.settings:get('enable_cheat_detection_automod') or false
  18. local automod_type = minetest.settings:get('cheat_detection_automod_type') or "ban"
  19. local automod_reason = minetest.settings:get('cheat_detection_automod_reason') or "Excessive Cheating Attempts"
  20. local cheat_detection_step = tonumber(minetest.settings:get("cheat_detection_step")) or 0
  21. local server_step = tonumber(minetest.settings:get("dedicated_server_step")) or 0.1
  22. local patience_meter = 3
  23. local cheat_patience_meter = 3
  24. local node_under_height = 0.7
  25. local server_host = minetest.settings:get("name") or ""
  26. local detection_list = {}
  27. local debug_hud_list = {}
  28. if enable_automod and type(enable_automod) == "string" then
  29. if enable_automod == "true" then
  30. enable_automod = true
  31. elseif enable_automod == "false" then
  32. enable_automod = false
  33. end
  34. end
  35. local function get_velocity_as_whole_interger(player, dir)
  36. local result = nil
  37. local velocity = player:get_player_velocity()
  38. local vel_x, vel_z = nil
  39. if dir == "horizontal" then
  40. local speed = nil
  41. vel_x = math.floor(velocity.x)
  42. vel_z = math.floor(velocity.z)
  43. if vel_x < 0 and vel_z >= 0 then
  44. vel_x = math.abs(vel_x)
  45. elseif vel_z < 0 and vel_x >= 0 then
  46. vel_z = math.abs(vel_x)
  47. end
  48. if vel_x > vel_z then
  49. speed = math.abs(vel_x)
  50. elseif vel_z > vel_x then
  51. speed = math.abs(vel_z)
  52. elseif vel_x == vel_z then
  53. speed = math.abs(vel_x or vel_z)
  54. end
  55. result = speed
  56. elseif dir == "vertical" then
  57. result = velocity.y
  58. end
  59. return result
  60. end
  61. --Patch for unknown block detection skipping, we really just need to get useful properties only anyway
  62. local function verify_node(node)
  63. local def = minetest.registered_nodes[node.name]
  64. --Is it a undefined block? if so generate some properties for it
  65. if def == nil then
  66. def = {walkable = true, drawtype = "normal"}
  67. end
  68. return def
  69. end
  70. local function add_tracker(player)
  71. local name = player:get_player_name()
  72. if not detection_list[name] then
  73. detection_list[name] = {
  74. suspicion = "None",
  75. prev_pos = {x = 0, y = 0, z = 0},
  76. prev_velocity = {x = 0, y = 0, z = 0},
  77. strikes = 0,
  78. patience_cooldown = 2,
  79. logged_in = true,
  80. logged_in_cooldown = 4,
  81. li_cd_full_time = 0,
  82. automod_triggers = 0,
  83. instant_punch_time = 0,
  84. anticheat_callout_time = 0,
  85. unhold_sneak_time = 0,
  86. liquid_walk_time = 0,
  87. flight_time = 0,
  88. time_resetted = false,
  89. falling_stops_time = 0,
  90. node_clipping_time = 0,
  91. flying = false,
  92. alert_sent = false,
  93. punched = false,
  94. falling = false,
  95. killaura = false,
  96. fast_dig = false,
  97. instant_break = false,
  98. abnormal_range = false,
  99. killaura_check = false,
  100. }
  101. end
  102. end
  103. local function update_tracker_info(player, list)
  104. local name = player:get_player_name()
  105. if detection_list[name] then
  106. detection_list[name] = list
  107. end
  108. end
  109. local function remove_tracker(player)
  110. local name = player:get_player_name()
  111. if detection_list[name] then
  112. detection_list[name] = nil
  113. end
  114. end
  115. local function get_tracker(player)
  116. local name = player:get_player_name()
  117. local result = nil
  118. if detection_list[name] then
  119. result = detection_list[name]
  120. end
  121. return result
  122. end
  123. local function cast_ray_under_player(player, range, objects, liquids)
  124. local pos = player:get_pos()
  125. objects = objects or false
  126. liquids = liquids or false
  127. --Raycast stuff.
  128. local ray_start = vector.add({x = pos.x, y = pos.y - 1, z = pos.z}, {x=0, y=0, z=0})
  129. local ray_modif = vector.multiply({x = 0, y = -0.9, z = 0}, range) --point ray down
  130. local ray_end = vector.add(ray_start, ray_modif)
  131. local ray = minetest.raycast(ray_start, ray_end, objects, liquids)
  132. local object = ray:next()
  133. --Skip player's collision
  134. if object and object.type == "object" and object.ref == player then
  135. object = ray:next()
  136. end
  137. return object
  138. end
  139. local function cast_ray_under_pos(pos, range, objects, liquids)
  140. objects = objects or false
  141. liquids = liquids or false
  142. --Raycast stuff.
  143. local ray_start = vector.add({x = pos.x, y = pos.y - 0.1, z = pos.z}, {x=0, y=0, z=0})
  144. local ray_modif = vector.multiply({x = 0, y = -0.9, z = 0}, range) --point ray down
  145. local ray_end = vector.add(ray_start, ray_modif)
  146. local ray = minetest.raycast(ray_start, ray_end, objects, liquids)
  147. local object = ray:next()
  148. --
  149. return object
  150. end
  151. --check if player has a obstacle underneath him (Returns a boolean)
  152. local function check_obstacle_found_under(player, range)
  153. local result = false
  154. local object = cast_ray_under_player(player, range, true, true)
  155. if object and object.type then
  156. result = true
  157. end
  158. return result
  159. end
  160. --check if player has a obstacle underneath him and get their properties (Returns a table of properties, if failed returns nil)
  161. local function get_obstacle_found_under(player, range)
  162. local result = nil
  163. local object = cast_ray_under_player(player, range, true, true)
  164. if object and object.type then
  165. if object.type == "node" then
  166. local node = minetest.get_node(object.under)
  167. --We need to make sure this raycast does not grab air as it's final target
  168. if node then
  169. local def = verify_node(node)
  170. result = {name = node.name, type = "node", def = def}
  171. end
  172. elseif object.type == "object" and object.ref and not(object.ref:is_player() or object.ref == player) then
  173. local entity = object.ref:get_luaentity()
  174. result = {name = entity.name, type = "entity", def = entity}
  175. end
  176. end
  177. return result
  178. end
  179. --check if position has a node underneath it and get their properties (Returns a table of properties, if failed returns nil)
  180. local function get_node_under_ray(pos, range)
  181. local result = nil
  182. local object = cast_ray_under_pos(pos, range, false, true)
  183. if object and object.type and object.type == "node" then
  184. local node = minetest.get_node(object.under)
  185. --We need to make sure this raycast does not grab air as it's final target
  186. if node then
  187. local def = verify_node(node)
  188. result = def
  189. end
  190. end
  191. return result
  192. end
  193. --needed for flight check and jesus walk checks to prevent false positives by entity collision
  194. local function check_if_entity_under(pos)
  195. local entities = minetest.get_objects_inside_radius({x = pos.x, y = pos.y, z = pos.z}, 1)
  196. local result = false
  197. --look for physical objects only (TODO: convert method to raycast)
  198. for _,entity in pairs(entities) do
  199. if not entity:is_player() and entity:get_luaentity().physical == true then
  200. result = true
  201. break
  202. end
  203. end
  204. return result
  205. end
  206. local function check_player_is_inside_nodes(player)
  207. local pos = player:get_pos()
  208. local node_top = minetest.get_node({x = pos.x, y = pos.y + 1, z = pos.z})
  209. local node_bottom = minetest.get_node(pos)
  210. local result = false
  211. if node_top and node_bottom then
  212. node_top = minetest.registered_nodes[node_top.name]
  213. node_bottom = minetest.registered_nodes[node_bottom.name]
  214. if node_top and node_top.walkable and node_bottom and node_bottom.walkable then
  215. result = true
  216. end
  217. end
  218. return result
  219. end
  220. local function check_player_is_swimming(player)
  221. local pos = player:get_pos()
  222. local node_top = minetest.get_node({x = pos.x, y = pos.y + 1, z = pos.z})
  223. local node_bottom = minetest.get_node(pos)
  224. local result = false
  225. if node_top and node_bottom then
  226. node_top = minetest.registered_nodes[node_top.name]
  227. node_bottom = minetest.registered_nodes[node_bottom.name]
  228. if type(node_top) == "table"
  229. and type(node_bottom) == "table"
  230. and (node_top.drawtype == "liquid"
  231. or node_top.drawtype == "flowingliquid"
  232. or node_bottom.drawtype == "liquid"
  233. or node_bottom.drawtype == "flowingliquid") then
  234. result = true
  235. end
  236. end
  237. return result
  238. end
  239. local function is_solid_node_under(pos, max_height)
  240. local result = false
  241. local y_steps = 0
  242. local found = false
  243. while max_height > y_steps do
  244. local node = minetest.get_node({x = pos.x, y = pos.y - y_steps, z = pos.z})
  245. 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
  246. found = true
  247. result = true
  248. elseif not minetest.registered_nodes[node.name] then --unknown block
  249. found = true
  250. result = true
  251. end
  252. if found then
  253. print(node.name)
  254. break
  255. end
  256. y_steps = y_steps + 1
  257. end
  258. return result
  259. end
  260. --Check surroundings for nodes in a 3x3 block order (Returns specified node property value if successful, if not then it returns nil)
  261. local function check_surrounding_for_nodes(height, pos)
  262. local result = false
  263. local scan_tries = 8
  264. --TODO: false positive - unable to grab slabs, fences and banners with raycast, make standard get_node() failsafe [Sneak Key] *facepalm...
  265. --Only scan for nearby nodes by 3x3 blocks
  266. while scan_tries > 0 do
  267. local new_pos = nil
  268. local node = nil
  269. if scan_tries == 8 then
  270. new_pos = {x = pos.x, y = pos.y + height, z = pos.z - 1}
  271. node = is_solid_node_under(new_pos, 4)
  272. elseif scan_tries == 7 then
  273. new_pos = {x = pos.x - 1, y = pos.y + height, z = pos.z - 1}
  274. node = is_solid_node_under(new_pos, 4)
  275. elseif scan_tries == 6 then
  276. new_pos = {x = pos.x + 1, y = pos.y + height, z = pos.z + 1}
  277. node = is_solid_node_under(new_pos, 4)
  278. elseif scan_tries == 5 then
  279. new_pos = {x = pos.x - 1, y = pos.y + height, z = pos.z}
  280. node = is_solid_node_under(new_pos, 4)
  281. elseif scan_tries == 4 then
  282. new_pos = {x = pos.x - 1, y = pos.y + height, z = pos.z + 1}
  283. node = is_solid_node_under(new_pos, 4)
  284. elseif scan_tries == 3 then
  285. new_pos = {x = pos.x + 1, y = pos.y + height, z = pos.z - 1}
  286. node = is_solid_node_under(new_pos, 4)
  287. elseif scan_tries == 2 then
  288. new_pos = {x = pos.x + 1, y = pos.y + height, z = pos.z}
  289. node = is_solid_node_under(new_pos, 4)
  290. elseif scan_tries == 1 then
  291. new_pos = {x = pos.x, y = pos.y + height, z = pos.z + 1}
  292. node = is_solid_node_under(new_pos, 4)
  293. end
  294. print(tostring(scan_tries)..") "..tostring(node))
  295. if node == true then
  296. result = node
  297. break
  298. end
  299. scan_tries = scan_tries - 1
  300. end
  301. return result
  302. end
  303. --Alert staff if goon is pulling hacks out of his own ass
  304. local function send_alert_to_serverstaff(suspect, suspicion)
  305. local players = minetest.get_connected_players()
  306. for _,player in pairs(players) do
  307. local name = player:get_player_name()
  308. local info = get_tracker(player)
  309. local is_staff = minetest.check_player_privs(name, {ban=true})
  310. --Do not spam these alerts more than once per accusation since staff can get annoyed by accusation spam
  311. if is_staff == true then
  312. 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!"))
  313. end
  314. end
  315. end
  316. local function check_if_forced_flying(player, info, pos, velocity, avg_rtt)
  317. local result = false
  318. --Skip flight check if punched or logged in
  319. if info.logged_in == true then
  320. return result
  321. elseif info.punched == true then
  322. return result
  323. end
  324. local name = player:get_player_name()
  325. local can_fly = minetest.check_player_privs(name, {fly=true})
  326. local min_speed = tonumber(minetest.settings:get("movement_speed_fast")) or 20
  327. local min_jump = tonumber(minetest.settings:get("movement_speed_jump")) or 6.5
  328. local speed_mod = tonumber(player:get_physics_override().speed)
  329. local node_under = is_solid_node_under(pos, 1)
  330. local sneak_hold = player:get_player_control().sneak or false
  331. local object_under = check_obstacle_found_under(player, 1)
  332. local suspicious = false
  333. local delay = tonumber(patience_meter + avg_rtt)
  334. local node_type = ""
  335. min_speed = math.floor(speed_mod * min_speed)
  336. --Reset sneak unhold time if player is sneak glitching
  337. if sneak_hold == true then
  338. info.unhold_sneak_time = 0
  339. end
  340. -- print(node_under, object_under, sneak_hold)
  341. if info.flying == true then
  342. suspicious = true
  343. end
  344. --check if they are standing still while hovering in the air
  345. 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
  346. local was_falling = info.falling or false
  347. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." triggered the Hover Check.")
  348. object_under = check_if_entity_under(pos)
  349. --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
  350. if was_falling == true or object_under == true then
  351. info.flight_time = 0
  352. info.falling_stops_time = 0
  353. if was_falling == true then
  354. info.falling = false
  355. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." was falling down but was halted by unloaded blocks, No suspicious activity found.")
  356. elseif object_under == true then
  357. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." is standing on a entity obstacle, No suspicious activity found.")
  358. end
  359. return result
  360. end
  361. local nodes_around = check_surrounding_for_nodes(2, pos)
  362. if nodes_around == false then
  363. info.unhold_sneak_time = info.unhold_sneak_time + 1
  364. end
  365. --print("Sneak Unhold Time: "..tostring(info.unhold_sneak_time))
  366. --Get triggered if the player has been caught constantly hovering up in the air
  367. if info.unhold_sneak_time >= delay then
  368. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is hovering, Server has marked this as suspicious activity!")
  369. suspicious = true
  370. end
  371. --Check if player is indeed flying
  372. elseif node_under == false and object_under == false and can_fly == false and velocity.y > min_jump and info.flying == false then
  373. if info.flying == true then
  374. return true
  375. end
  376. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." triggered the Flight Check.")
  377. --Reset Falling Stops Time due to Flight check
  378. info.falling_stops_time = 0
  379. info.unhold_sneak_time = 0
  380. info.falling = false
  381. --Check for vertical velocity change to determine if the player is actually flying
  382. local new_velocity = player:get_player_velocity()
  383. --Get triggered if the player has been constantly climbing up for far too long at a very steady pace
  384. if info.flight_time >= delay then
  385. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is continuously increasing vertical velocity too many times. Server has marked this as suspicious activity!")
  386. suspicious = true
  387. --If bastard is constantly acsending steadily at max speed, then he's flying like a little twat. add flight time
  388. elseif new_velocity.y == min_speed then
  389. info.flight_time = info.flight_time + 1
  390. --If a player suddently begins dropping vertical velocity, then they might be falling
  391. elseif new_velocity.y < info.prev_velocity.y then
  392. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." seem to be dropping vertical velocity due to falling. No suspicious activity found.")
  393. --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
  394. elseif new_velocity.y >= info.prev_velocity.y and new_velocity.y < min_speed then
  395. info.prev_velocity = new_velocity
  396. info.flight_time = info.flight_time + 1
  397. end
  398. --print("Flight Time: "..tostring(info.flight_time))
  399. --Check if player is falling, or flying (false positives found)
  400. elseif node_under == false and object_under == false and can_fly == false and velocity.y < -min_jump and info.flying == false then
  401. if info.flying == true then
  402. return true
  403. end
  404. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." triggered the Fall Check.")
  405. --Reset Flight Time due to Falling check
  406. info.flight_time = 0
  407. info.unhold_sneak_time = 0
  408. --Check for vertical velocity change to determine if the player is actually flying
  409. local new_velocity = player:get_player_velocity()
  410. --print(new_velocity.y, info.prev_velocity.y)
  411. --If still falling, then stop this...
  412. if info.falling == true then
  413. info.prev_velocity = new_velocity
  414. info.falling_stops_time = 0
  415. return result
  416. --Get triggered if the player has been constantly stopping from falling for far too long at a very steady pace
  417. elseif info.falling_stops_time >= delay then
  418. info.prev_velocity = new_velocity
  419. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is continuously stopping from falling down too many times. Server has marked this as suspicious activity!")
  420. suspicious = true
  421. --If bastard is constantly decsending steadily at max speed, then he's flying like a little twat. add falling stop time
  422. elseif new_velocity.y == -min_speed then
  423. info.prev_velocity = new_velocity
  424. info.falling_stops_time = info.falling_stops_time + 1
  425. --if falling down, reset timer
  426. elseif new_velocity.y < info.prev_velocity.y and new_velocity.y > -min_speed then
  427. info.prev_velocity = new_velocity
  428. info.falling_stops_time = 0
  429. --If a player suddently begins dropping vertical velocity above max speed, then they are indeed falling
  430. elseif new_velocity.y < -min_speed then
  431. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." is confirmed falling. No suspicious activity found.")
  432. info.prev_velocity = new_velocity
  433. info.falling = true
  434. is_solid_node
  435. --If a player is falling, they can't be able to suddenly increase vertical velocity or make their velocity stay the same
  436. elseif new_velocity.y >= info.prev_velocity.y then
  437. minetest.log("action", "[CHEAT DETECTION]: Player "..name.." suddenly stopped falling (Could be due to unloaded blocks or lag), verifying behavior...")
  438. info.prev_velocity = new_velocity
  439. info.falling_stops_time = info.falling_stops_time + 1
  440. end
  441. --Get triggered if the player has been constantly climbing up for far too long
  442. if info.falling_stops_time >= delay then
  443. suspicious = true
  444. end
  445. --print("Fall Time: "..tostring(info.falling_stops_time))
  446. --Just a normal player doing normal things, reset timers
  447. elseif node_under == true or object_under == true then
  448. suspicious = false
  449. info.flying = false
  450. info.falling = false
  451. info.unhold_sneak_time = 0
  452. info.flight_time = 0
  453. info.falling_stops_time = 0
  454. end
  455. if suspicious == true then
  456. info.flying = true
  457. result = true
  458. end
  459. return result
  460. end
  461. local function check_if_forced_noclipping(player, info, velocity, avg_rtt)
  462. local result = false
  463. --Skip Noclip check if punched
  464. if info.punched == true then
  465. return result
  466. end
  467. local name = player:get_player_name()
  468. local delay = tonumber(patience_meter + avg_rtt)
  469. local can_noclip = minetest.check_player_privs(name, {noclip=true})
  470. local inside_nodes = check_player_is_inside_nodes(player)
  471. --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
  472. 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
  473. info.node_clipping_time = info.node_clipping_time + 1
  474. if info.node_clipping_time > delay then
  475. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is clipping through solid nodes while moving without noclip privileges. Server has marked this as suspicious activity!")
  476. info.strikes = 3
  477. result = true
  478. end
  479. else
  480. info.node_clipping_time = 0
  481. end
  482. return result
  483. end
  484. local function check_if_forced_fast(player, info)
  485. local result = false
  486. --Skip Jesus Walk check if punched
  487. if info.punched == true then
  488. return result
  489. end
  490. local aux_pressed = player:get_player_control().aux1
  491. --if player is not pressing sprint key, skip this check
  492. if aux_pressed == false then
  493. return result
  494. end
  495. local name = player:get_player_name()
  496. local current_speed = get_velocity_as_whole_interger(player, "horizontal")
  497. local min_speed = tonumber(minetest.settings:get("movement_speed_fast")) or 20
  498. local detection_fast_speed = nil
  499. local can_fast = minetest.check_player_privs(name, {fast=true})
  500. local speed_mod = tonumber(player:get_physics_override().speed)
  501. local f = math.floor
  502. --This is needed to determine if user is speeding, subtract 1 for fast speed accuracy
  503. min_speed = math.floor(speed_mod * min_speed)
  504. detection_fast_speed = math.floor(min_speed - 1)
  505. if can_fast == false and (current_speed == min_speed or current_speed == detection_fast_speed) then
  506. 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!")
  507. result = true
  508. end
  509. return result
  510. end
  511. local function check_if_jesus_walking(player, info, pos, velocity, avg_rtt)
  512. local result = false
  513. --Skip Jesus Walk check if punched
  514. if info.punched == true then
  515. return result
  516. end
  517. local name = player:get_player_name()
  518. local obstacle_under = get_obstacle_found_under(player, 1)
  519. local node_under = "not found"
  520. local swimming = check_player_is_swimming(player)
  521. local sneak_hold = player:get_player_control().sneak or false
  522. local delay = tonumber(patience_meter + avg_rtt)
  523. if obstacle_under and obstacle_under.type == "node" and obstacle_under.def.drawtype then
  524. node_under = obstacle_under.def.drawtype
  525. end
  526. --If someone is able to stand still on a liquid type node, then they are clearly walking on water
  527. 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
  528. local object_under = check_if_entity_under(pos)
  529. if object_under == false then
  530. info.node_clipping_time = info.node_clipping_time + 1
  531. info.liquid_walk_time = info.liquid_walk_time + 1
  532. print("Liquid Walk Time: "..tostring(info.liquid_walk_time))
  533. end
  534. else
  535. info.liquid_walk_time = 0
  536. end
  537. --Get triggered if the player has been constantly walking on water for far too long
  538. if info.liquid_walk_time >= delay then
  539. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is litteraly standing on water, Server has marked this as suspicious activity!")
  540. info.strikes = 3
  541. result = true
  542. end
  543. return result
  544. end
  545. local function verify_suspicious_behavior(info, suspicion, avg_rtt)
  546. local timer_step = tonumber(cheat_detection_step + avg_rtt)
  547. minetest.after(timer_step, function()
  548. info.strikes = info.strikes + 1
  549. end)
  550. --Don't go past 3 strikes
  551. if info.strikes >= 3 then
  552. info.suspicion = suspicion
  553. info.strikes = 3
  554. info.patience_cooldown = 2
  555. end
  556. end
  557. --Enable Server Anti-Cheat System if Player Manager is Present, keep an eye out for suspicious activity
  558. local function handle_cheat_detection()
  559. local players = minetest.get_connected_players()
  560. for _,player in pairs(players) do
  561. local pname = player:get_player_name()
  562. local pos = player:get_pos()
  563. local velocity = player:get_player_velocity()
  564. local is_superuser = minetest.check_player_privs(pname, {server=true})
  565. local pinfo = minetest.get_player_information(pname) --this is important
  566. local info = get_tracker(player)
  567. local smite = false
  568. if pinfo and info and pname ~= server_host and is_superuser == false then
  569. --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
  570. if info.logged_in == true and info.logged_in_cooldown > 0 and cheat_detection_step < 0.3 and server_step <= 0.1 then
  571. info.li_cd_full_time = info.li_cd_full_time + 1
  572. if info.li_cd_full_time > 15 then
  573. info.logged_in_cooldown = info.logged_in_cooldown - 1
  574. info.li_cd_full_time = 0
  575. end
  576. if info.logged_in_cooldown < 1 then
  577. info.logged_in = false
  578. info.logged_in_cooldown = nil
  579. end
  580. --If detection step is at a balanced rate, then normally count down without any further delay
  581. elseif info.logged_in == true and info.logged_in_cooldown > 0 and (cheat_detection_step >= 0.3 or server_step >= 0.1) then
  582. info.logged_in_cooldown = info.logged_in_cooldown - 1
  583. if info.logged_in_cooldown < 1 then
  584. info.logged_in = false
  585. info.logged_in_cooldown = nil
  586. end
  587. end
  588. --Scan players every single average round trip time for accuracy
  589. minetest.after(pinfo.avg_rtt, function()
  590. info.time_resetted = false
  591. --Only do debug for dummy test hacker client to see what's up whith him
  592. if pname == "haxor" then
  593. update_debug_hud(player, false)
  594. end
  595. local is_jesus_walking = check_if_jesus_walking(player, info, pos, velocity, pinfo.avg_rtt)
  596. local is_force_noclipping = check_if_forced_noclipping(player, info, velocity, pinfo.avg_rtt)
  597. local is_force_fast = check_if_forced_fast(player, info)
  598. local is_force_flying = check_if_forced_flying(player, info, pos, velocity, pinfo.avg_rtt)
  599. --Hmm, I sense suspicious activity in this sector... [Killaura]
  600. if info.killaura == true then
  601. verify_suspicious_behavior(info, "Killaura", pinfo.avg_rtt)
  602. info.killaura_check = false
  603. info.killaura = false
  604. --Hmm, I sense suspicious activity in this sector... [Unlimited Range]
  605. elseif info.abnormal_range == true then
  606. verify_suspicious_behavior(info, "Unlimited Range", pinfo.avg_rtt)
  607. info.abnormal_range = false
  608. --Hmm, I sense suspicious activity in this sector... [Instant Break]
  609. elseif info.instant_break == true then
  610. verify_suspicious_behavior(info, "Instant Node Break", pinfo.avg_rtt)
  611. info.instant_break = false
  612. --Hmm, I sense suspicious activity in this sector... [Fast Dig]
  613. elseif info.fast_dig == true then
  614. verify_suspicious_behavior(info, "Fast Dig", pinfo.avg_rtt)
  615. info.fast_dig = false
  616. --Hmm, I sense suspicious activity in this sector... [Walk on Water Hacks]
  617. elseif is_jesus_walking == true then
  618. verify_suspicious_behavior(info, "Jesus Walk", pinfo.avg_rtt)
  619. --Hmm, I sense suspicious activity in this sector... [Noclip Hacks]
  620. elseif is_force_noclipping == true then
  621. verify_suspicious_behavior(info, "Forced Noclip", pinfo.avg_rtt)
  622. --Hmm, I sense suspicious activity in this sector... [Fast Hacks]
  623. elseif is_force_fast == true then
  624. verify_suspicious_behavior(info, "Forced Fast", pinfo.avg_rtt)
  625. --Hmm, I sense suspicious activity in this sector... [Fly Hacks]
  626. elseif is_force_flying == true then
  627. verify_suspicious_behavior(info, "Forced Fly", pinfo.avg_rtt)
  628. --So far so good, nothing to see here (Reset timers and strikes)
  629. else
  630. info.patience_cooldown = info.patience_cooldown - 1
  631. if info.patience_cooldown < 1 then
  632. info.time_resetted = true
  633. info.automod_triggers = 0
  634. info.patience_cooldown = 2
  635. end
  636. end
  637. --Send Warning after 3 strikes, then reset. Following up with patience meter to drop
  638. if info.strikes == 3 and info.suspicion ~= "None" then
  639. send_alert_to_serverstaff(pname, info.suspicion)
  640. if info.alert_sent == false then
  641. minetest.log("warning", "[CHEAT DETECTION]: Player "..pname.." have been flagged by the Server for possibly using a Hacked Client!")
  642. 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!"))
  643. info.alert_sent = true
  644. end
  645. if enable_automod == true then
  646. local delay = tonumber(patience_meter + pinfo.avg_rtt)
  647. info.automod_triggers = info.automod_triggers + 1
  648. if info.automod_triggers >= delay then
  649. smite = true
  650. end
  651. end
  652. info.strikes = 0
  653. elseif info.strikes == 3 and info.suspicion == "None" then
  654. info.strikes = 0
  655. end
  656. --I ran out of patience, please for the love of god Let me BAN this sneaky little twat NOW!!!
  657. if smite and enable_automod == true then
  658. info.automod_triggers = 0
  659. if automod_type == "kick" then
  660. minetest.log("action", "[CHEAT DETECTION]: Server has kicked "..pname.." for performing continuous abnormal behaviors while in-game.")
  661. minetest.kick_player(pname, "Cheat Detection: "..automod_reason)
  662. elseif automod_type == "ban" then
  663. minetest.log("action", "[CHEAT DETECTION]: Server has banned "..pname.." for performing continuous abnormal behaviors while in-game.")
  664. minetest.ban_player(pname)
  665. end
  666. end
  667. info.punched = false
  668. info.prev_velocity = velocity
  669. info.prev_pos = pos
  670. update_tracker_info(player, info)
  671. end)
  672. end
  673. end
  674. minetest.after(cheat_detection_step, function()
  675. handle_cheat_detection()
  676. end)
  677. end
  678. minetest.register_on_mods_loaded(function()
  679. handle_cheat_detection()
  680. end)
  681. minetest.register_on_leaveplayer(function(player)
  682. remove_tracker(player)
  683. update_debug_hud(player, true)
  684. end)
  685. minetest.register_on_joinplayer(function(player)
  686. --Add the dang tracker onto the specified player
  687. add_tracker(player)
  688. end)
  689. --Additional Anti-Hackerman stuff
  690. minetest.register_on_cheat(function(player, cheat)
  691. local info = get_tracker(player)
  692. --Skip shenanigain check if player is punched, this is for knockback exceptions
  693. if info.punched == true or info.killaura_check == true then
  694. return
  695. end
  696. local name = player:get_player_name()
  697. local pinfo = minetest.get_player_information(name)
  698. local delay = tonumber(cheat_patience_meter + pinfo.avg_rtt)
  699. local accusation = nil
  700. if cheat.type == "interacted_too_far" then
  701. info.anticheat_callout_time = info.anticheat_callout_time + 1
  702. accusation = "unlimitedrange"
  703. elseif cheat.type == "dug_unbreakable" then
  704. info.anticheat_callout_time = info.anticheat_callout_time + 1
  705. accusation = "instantbreak"
  706. elseif cheat.type == "dug_too_fast" then
  707. info.anticheat_callout_time = info.anticheat_callout_time + 1
  708. accusation = "fastdig"
  709. end
  710. --Hackers normally do things very quickly
  711. if accusation and info.anticheat_callout_time > delay then
  712. if accusation == "instantbreak" then
  713. info.instant_break = true
  714. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is trying to instantly break unbreakable nodes too many times. Server has marked this as suspicious activity!")
  715. elseif accusation == "fastdig" then
  716. info.fast_dig = true
  717. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is trying to instantly dig nodes too many times. Server has marked this as suspicious activity!")
  718. elseif accusation == "unlimitedrange" then
  719. info.abnormal_range = true
  720. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is trying to do long range interactions too many times. Server has marked this as suspicious activity!")
  721. end
  722. info.anticheat_callout_time = 0
  723. end
  724. end)
  725. minetest.register_on_punchplayer(function(player, hitter, punchtime)
  726. local info = get_tracker(player)
  727. local name = nil
  728. local info2 = nil
  729. local pinfo = nil
  730. local delay = nil
  731. --Knockback Exceptions
  732. if info then
  733. info.punched = true
  734. end
  735. if hitter:is_player() then
  736. name = hitter:get_player_name()
  737. info2 = get_tracker(hitter)
  738. pinfo = minetest.get_player_information(name)
  739. delay = tonumber(patience_meter + pinfo.avg_rtt - 1)
  740. end
  741. --killaura detection, there is absolutely no flipping way any normal human being can land a 0 second punch repeatedly (No Mercy for these scrubs)
  742. if info2 and punchtime <= 0 then
  743. info2.killaura_check = true
  744. info2.instant_punch_time = info2.instant_punch_time + 1
  745. --Confirm killaura behavior if player instantly punched a player too many times
  746. if info2.instant_punch_time > delay then
  747. info2.killaura = true
  748. minetest.log("warning", "[CHEAT DETECTION]: Player "..name.." is instantly punching players too many times. Server has marked this as suspicious activity!")
  749. end
  750. elseif info2 and punchtime > 0 then
  751. info2.instant_punch_time = 0
  752. end
  753. end)