2016-08-29 00:20:26 -07:00
-- ANTI CHEAT by rnd
-- Copyright 2016 rnd
-- includes modified/bugfixed spectator mod by jp
-------------------------------------------------------------------------
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU 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 General Public License for more details.
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------
local cheat = { } ;
2017-09-08 00:53:05 -07:00
local version = " 09/08/2017 " ;
2016-08-29 00:20:26 -07:00
anticheatsettings = { } ;
dofile ( minetest.get_modpath ( " anticheat " ) .. " /settings.lua " )
local CHEAT_TIMESTEP = anticheatsettings.CHEAT_TIMESTEP ;
local CHECK_AGAIN = anticheatsettings.CHECK_AGAIN ;
cheat.moderators = anticheatsettings.moderators ;
2016-09-12 07:51:41 -07:00
2016-08-29 00:20:26 -07:00
2016-09-12 06:38:02 -07:00
anticheatdb = { } ; -- data about detected cheaters
2016-08-31 12:07:33 -07:00
cheat.suspect = " " ;
2016-10-06 07:17:15 -07:00
cheat.players = { } ; -- temporary cheat detection db
2016-08-29 00:20:26 -07:00
cheat.message = " " ;
2016-09-14 02:42:06 -07:00
cheat.debuglist = { } ; -- [name]=true -- who gets to see debug msgs
2016-10-06 07:17:15 -07:00
cheat.scan_timer = 0 ; -- global scan of players
cheat.stat_timer = 0 ; -- used to collect stats
2016-08-29 00:20:26 -07:00
cheat.nodelist = { } ;
cheat.watcher = { } ; -- list of watchers
cheat.timestep = CHEAT_TIMESTEP ;
-- list of forbidden nodes
cheat.nodelist = { [ " default:stone " ] = false , [ " default:cobble " ] = false , [ " default:dirt " ] = false , [ " default:sand " ] = false , [ " default:tree " ] = false } ;
local punish_cheat = function ( name )
2018-02-17 20:44:46 -08:00
local player = minetest.get_player_by_name ( name ) ;
2016-09-12 06:38:02 -07:00
local ip = tostring ( minetest.get_player_ip ( name ) ) ;
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
if not player then return end
local text = " " ; local logtext = " " ;
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
if cheat.players [ name ] . cheattype == 1 then
2016-09-12 22:58:45 -07:00
text = " #anticheat: " .. name .. " was caught walking inside wall " ;
2016-09-14 02:42:06 -07:00
logtext = os.date ( " %H:%M.%S " ) .. " #anticheat: " .. name .. " was caught walking inside wall at " .. minetest.pos_to_string ( cheat.players [ name ] . cheatpos ) ;
2016-08-29 00:20:26 -07:00
player : set_hp ( 0 ) ;
elseif cheat.players [ name ] . cheattype == 2 then
2018-02-17 20:44:46 -08:00
2016-12-26 15:47:15 -08:00
local gravity = player : get_physics_override ( ) . gravity ; if gravity < 1 then return end
2016-09-14 02:42:06 -07:00
logtext = os.date ( " %H:%M.%S " ) .. " #anticheat: " .. name .. " was caught flying at " .. minetest.pos_to_string ( cheat.players [ name ] . cheatpos ) ;
2016-09-20 04:53:49 -07:00
if cheat.players [ name ] . cheatpos.y > 5 then -- only above height 5 it directly damages flyer
text = " #anticheat: " .. name .. " was caught flying " ;
2016-10-22 02:02:14 -07:00
--player:set_hp(0);
2016-09-20 04:53:49 -07:00
end
2016-08-29 00:20:26 -07:00
end
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
if text ~= " " then
minetest.chat_send_all ( text ) ;
2018-02-17 20:44:46 -08:00
end
2016-09-20 04:53:49 -07:00
if logtext ~= " " then
2016-08-29 00:20:26 -07:00
minetest.log ( " action " , logtext ) ;
cheat.message = logtext ;
2018-02-17 20:44:46 -08:00
2016-09-12 07:51:41 -07:00
anticheatdb [ ip ] = { name = name , msg = logtext } ;
2016-09-12 06:38:02 -07:00
2016-08-29 00:20:26 -07:00
cheat.players [ name ] . count = 0 ; -- reset counter
cheat.players [ name ] . cheattype = 0 ;
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
for name , _ in pairs ( cheat.moderators ) do -- display full message to moderators
minetest.chat_send_player ( name , logtext ) ;
end
end
end
2017-09-08 00:53:05 -07:00
-- CHECKS
2016-08-29 00:20:26 -07:00
2017-09-08 00:53:05 -07:00
-- DETAILED NOCLIP CHECK
2018-02-17 20:44:46 -08:00
local check_noclip = function ( pos )
2017-09-08 00:53:05 -07:00
local nodename = minetest.get_node ( pos ) . name ;
local clear = true ;
if nodename ~= " air " then -- check if forbidden material!
clear = cheat.nodelist [ nodename ] ; -- test clip violation
if clear == nil then clear = true end
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if not clear then -- more detailed check
local anodes = minetest.find_nodes_in_area ( { x = pos.x - 1 , y = pos.y - 1 , z = pos.z - 1 } , { x = pos.x + 1 , y = pos.y + 1 , z = pos.z + 1 } , { " air " } ) ;
if # anodes == 0 then return false end
clear = true ;
end
return clear ;
end
-- DETAILED FLY CHECK
local check_fly = function ( pos ) -- return true if player not flying
local fly = ( minetest.get_node ( pos ) . name == " air " and minetest.get_node ( { x = pos.x , y = pos.y - 1 , z = pos.z } ) . name == " air " ) ; -- prerequisite for flying is this to be "air", but not sufficient condition
if not fly then return true end ;
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
local anodes = minetest.find_nodes_in_area ( { x = pos.x - 1 , y = pos.y - 1 , z = pos.z - 1 } , { x = pos.x + 1 , y = pos.y , z = pos.z + 1 } , { " air " } ) ;
if # anodes == 18 then -- player standing on air?
return false
else
2018-02-17 20:44:46 -08:00
return true
2017-09-08 00:53:05 -07:00
end
end
local round = function ( x )
2018-02-17 20:44:46 -08:00
if x > 0 then
return math.floor ( x + 0.5 )
2017-09-08 00:53:05 -07:00
else
2018-02-17 20:44:46 -08:00
return - math.floor ( - x + 0.5 )
2017-09-08 00:53:05 -07:00
end
end
--main check routine
local check_player = function ( player )
local name = player : get_player_name ( ) ;
local privs = minetest.get_player_privs ( name ) . kick ; if privs then return end -- dont check moderators
if cheat.watcher [ name ] then return end -- skip checking watchers while they watch
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
local pos = player : getpos ( ) ; -- feet position
pos.x = round ( pos.x * 10 ) / 10 ; pos.z = round ( pos.z * 10 ) / 10 ; -- less useless clutter
pos.y = round ( pos.y * 10 ) / 10 ; -- minetest buggy collision - need to do this or it returns wrong materials for feet position: aka magic number 0.498?????228
if pos.y < 0 then pos.y = pos.y + 1 end -- weird, without this it fails to check feet block where y<0, it checks one below feet
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
local nodename = minetest.get_node ( pos ) . name ;
local clear = true ;
if nodename ~= " air " then -- check if forbidden material!
clear = cheat.nodelist [ nodename ] ; -- test clip violation
if clear == nil then clear = true end
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
local fly = ( nodename == " air " and minetest.get_node ( { x = pos.x , y = pos.y - 1 , z = pos.z } ) . name == " air " ) ; -- prerequisite for flying, but not sufficient condition
if cheat.players [ name ] . count == 0 then -- player hasnt "cheated" yet, remember last clear position
cheat.players [ name ] . clearpos = cheat.players [ name ] . lastpos
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
-- manage noclip cheats
if not clear then -- player caught inside walls
local moved = ( cheat.players [ name ] . lastpos.x ~= pos.x ) or ( cheat.players [ name ] . lastpos.y ~= pos.y ) or ( cheat.players [ name ] . lastpos.z ~= pos.z ) ;
if moved then -- if player stands still whole time do nothing
if cheat.players [ name ] . count == 0 then cheat.players [ name ] . cheatpos = pos end -- remember first position where player found inside wall
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if cheat.players [ name ] . count == 0 then
minetest.after ( CHECK_AGAIN + math.random ( 5 ) ,
function ( )
cheat.players [ name ] . count = 0 ;
if not check_noclip ( pos ) then
punish_cheat ( name ) -- we got a cheater!
else
cheat.players [ name ] . count = 0 ; -- reset
cheat.players [ name ] . cheattype = 0 ;
end
end
)
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if cheat.players [ name ] . count == 0 then -- mark as suspect
2018-02-17 20:44:46 -08:00
cheat.players [ name ] . count = 1 ;
2017-09-08 00:53:05 -07:00
cheat.players [ name ] . cheattype = 1 ;
end
end
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
-- manage flyers
if fly then
local fpos ;
fly , fpos = minetest.line_of_sight ( pos , { x = pos.x , y = pos.y - 4 , z = pos.z } , 1 ) ; --checking node maximal jump height below feet
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if fly then -- so we are in air, are we flying?
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if player : get_player_control ( ) . sneak then -- player sneaks, maybe on border?
--search 18 nodes to find non air
local anodes = minetest.find_nodes_in_area ( { x = pos.x - 1 , y = pos.y - 1 , z = pos.z - 1 } , { x = pos.x + 1 , y = pos.y , z = pos.z + 1 } , { " air " } ) ;
if # anodes < 18 then fly = false end
end -- if at this point fly = true means player is not standing on border
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if pos.y >= cheat.players [ name ] . lastpos.y and fly then -- we actually didnt go down from last time and not on border
-- was lastpos in air too?
local lastpos = cheat.players [ name ] . lastpos ;
local anodes = minetest.find_nodes_in_area ( { x = lastpos.x - 1 , y = lastpos.y - 1 , z = lastpos.z - 1 } , { x = lastpos.x + 1 , y = lastpos.y , z = lastpos.z + 1 } , { " air " } ) ;
if # anodes == 18 then fly = true else fly = false end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if fly then -- so now in air above previous position, which was in air too?
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if cheat.players [ name ] . count == 0 then cheat.players [ name ] . cheatpos = pos end -- remember first position where player found "cheating"
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if cheat.players [ name ] . count == 0 then
minetest.after ( CHECK_AGAIN ,
function ( )
cheat.players [ name ] . count = 0 ;
if not check_fly ( pos ) then
punish_cheat ( name ) -- we got a cheater!
else
2018-02-17 20:44:46 -08:00
cheat.players [ name ] . count = 0 ;
2017-09-08 00:53:05 -07:00
cheat.players [ name ] . cheattype = 0 ;
end
end
)
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
if cheat.players [ name ] . count == 0 then -- mark as suspect
2018-02-17 20:44:46 -08:00
cheat.players [ name ] . count = 1 ;
2017-09-08 00:53:05 -07:00
cheat.players [ name ] . cheattype = 2 ;
end
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
end
2018-02-17 20:44:46 -08:00
2017-09-08 00:53:05 -07:00
end
end
cheat.players [ name ] . lastpos = pos
end
2016-12-26 15:47:15 -08:00
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
minetest.register_globalstep ( function ( dtime )
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
cheat.scan_timer = cheat.scan_timer + dtime
2017-12-10 21:43:21 -08:00
local boneworld = false ;
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
-- GENERAL SCAN OF ALL PLAYERS
2018-02-17 20:44:46 -08:00
if cheat.scan_timer > cheat.timestep then
cheat.stat_timer = cheat.stat_timer + cheat.timestep ;
2016-10-06 07:17:15 -07:00
-- dig xp stats every 2 minutes
if boneworld and cheat.stat_timer > 120 then
cheat.stat_timer = 0 ;
local players = minetest.get_connected_players ( ) ;
for _ , player in pairs ( players ) do
local pname = player : get_player_name ( ) ;
if cheat.players [ pname ] . stats.state == 1 then -- only if dig xp loaded to prevent anomalous stats
2016-10-08 23:37:35 -07:00
if boneworld.digxp [ pname ] then
local deltadig = cheat.players [ pname ] . stats.digxp ;
cheat.players [ pname ] . stats.digxp = boneworld.digxp [ pname ] ;
deltadig = boneworld.digxp [ pname ] - deltadig ;
cheat.players [ pname ] . stats.deltadig = deltadig ;
2018-02-17 20:44:46 -08:00
2016-10-08 23:37:35 -07:00
if deltadig > cheat.players [ pname ] . stats.maxdeltadig then
cheat.players [ pname ] . stats.maxdeltadig = deltadig ;
end
2018-02-17 20:44:46 -08:00
2016-10-22 02:02:14 -07:00
if deltadig > 2 then -- unnaturally high deltadig
local ip = tostring ( minetest.get_player_ip ( pname ) ) ;
local logtext = os.date ( " %H:%M.%S " ) .. " #anticheat: " .. pname .. " (ip " .. ip .. " ) is mining resources too fast, deltadig " .. deltadig ;
anticheatdb [ ip ] = { name = pname , msg = logtext } ;
minetest.log ( " action " , logtext ) ;
end
2018-02-17 20:44:46 -08:00
2016-10-06 07:17:15 -07:00
end
end
end
end
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
cheat.timestep = CHEAT_TIMESTEP + ( 2 * math.random ( ) - 1 ) * 2 ; -- randomize step so its unpredictable
cheat.scan_timer = 0 ;
--local t = minetest.get_gametime();
local players = minetest.get_connected_players ( ) ;
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
for _ , player in pairs ( players ) do
check_player ( player ) ;
end
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
for name , _ in pairs ( cheat.debuglist ) do -- show suspects in debug
for _ , player in pairs ( players ) do
local pname = player : get_player_name ( ) ;
if cheat.players [ pname ] . count > 0 then
minetest.chat_send_player ( name , " name " .. pname .. " , cheat pos " .. minetest.pos_to_string ( cheat.players [ pname ] . cheatpos ) .. " last clear pos " .. minetest.pos_to_string ( cheat.players [ pname ] . clearpos ) .. " cheat type " .. cheat.players [ pname ] . cheattype .. " cheatcount " .. cheat.players [ pname ] . count ) ;
end
end
end
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
end
end )
2016-09-29 14:02:21 -07:00
-- long range dig check
2018-02-17 20:44:46 -08:00
local check_can_dig = function ( pos , digger )
2016-09-29 14:02:21 -07:00
local p = digger : getpos ( ) ;
2016-09-30 13:37:50 -07:00
if p.y < 0 then p.y = p.y + 2 else p.y = p.y + 1 end -- head position
2016-09-29 14:02:21 -07:00
local dist = math.max ( math.abs ( p.x - pos.x ) , math.abs ( p.y - pos.y ) , math.abs ( p.z - pos.z ) ) ;
2016-09-29 14:05:05 -07:00
2018-02-17 20:44:46 -08:00
2016-10-04 06:05:58 -07:00
if dist > 6 then -- here 5
2016-09-29 14:02:21 -07:00
dist = math.floor ( dist * 100 ) / 100 ;
local pname = digger : get_player_name ( ) ;
2016-10-22 02:02:14 -07:00
local logtext = os.date ( " %H:%M.%S " ) .. " #anticheat: long range dig " .. pname .. " , distance " .. dist .. " , pos " .. minetest.pos_to_string ( pos ) ;
2016-09-29 14:02:21 -07:00
for name , _ in pairs ( cheat.debuglist ) do -- show to all watchers
minetest.chat_send_player ( name , logtext )
end
local ip = tostring ( minetest.get_player_ip ( pname ) ) ;
anticheatdb [ ip ] = { name = pname , msg = logtext } ;
return false
end
2018-02-17 20:44:46 -08:00
2016-09-29 14:02:21 -07:00
return true
end
local set_check_can_dig = function ( name )
local tabl = minetest.registered_nodes [ name ] ;
if not tabl then return end
tabl.can_dig = check_can_dig ;
minetest.override_item ( name , { can_dig = check_can_dig } )
--minetest.register_node(":"..name, tabl);
end
2018-02-17 20:44:46 -08:00
minetest.after ( 0 ,
function ( )
2016-10-04 06:05:58 -07:00
set_check_can_dig ( " default:stone " ) ;
2016-09-29 14:02:21 -07:00
set_check_can_dig ( " default:stone_with_iron " ) ;
set_check_can_dig ( " default:stone_with_copper " ) ;
set_check_can_dig ( " default:stone_with_coal " ) ;
set_check_can_dig ( " default:stone_with_gold " ) ;
set_check_can_dig ( " default:stone_with_mese " ) ;
set_check_can_dig ( " default:stone_with_diamond " ) ;
end
)
2016-10-06 07:17:15 -07:00
-- DISABLED: lot of false positives
2016-09-14 02:42:06 -07:00
-- collects misc stats on players
2016-10-06 07:17:15 -07:00
-- minetest.register_on_cheat(
-- function(player, c)
-- local name = player:get_player_name(); if name == nil then return end
2018-02-17 20:44:46 -08:00
-- local stats = cheat.players[name].stats;
2016-10-06 07:17:15 -07:00
-- if not stats[c.type] then stats[c.type] = 0 end
-- stats[c.type]=stats[c.type]+1;
-- end
-- )
2016-09-14 02:42:06 -07:00
2016-08-29 00:20:26 -07:00
local watchers = { } ; -- for each player a list of watchers
minetest.register_on_joinplayer ( function ( player ) -- init stuff on player join
2018-02-17 20:44:46 -08:00
local name = player : get_player_name ( ) ; if name == nil then return end
2016-08-29 00:20:26 -07:00
local pos = player : getpos ( ) ;
2018-02-17 20:44:46 -08:00
2016-10-06 07:17:15 -07:00
if cheat.players [ name ] == nil then
cheat.players [ name ] = { count = 0 , cheatpos = pos , clearpos = pos , lastpos = pos , cheattype = 0 } ; -- type 0: none, 1 noclip, 2 fly
end
2018-02-17 20:44:46 -08:00
2016-10-06 07:17:15 -07:00
if cheat.players [ name ] and cheat.players [ name ] . stats == nil then
cheat.players [ name ] . stats = { maxdeltadig = 0 , deltadig = 0 , digxp = 0 , state = 0 } ; -- various statistics about player: max dig xp increase in 2 minutes, current dig xp increase
2018-02-17 20:44:46 -08:00
2016-10-06 07:17:15 -07:00
minetest.after ( 5 , -- load digxp after boneworld loads it
2018-02-17 20:44:46 -08:00
function ( )
2017-12-10 21:43:21 -08:00
local boneworld = false ;
2016-10-06 07:17:15 -07:00
if boneworld and boneworld.xp then
cheat.players [ name ] . stats.digxp = boneworld.digxp [ name ] or 0 ;
cheat.players [ name ] . stats.state = 1 ;
end
end
)
2018-02-17 20:44:46 -08:00
2016-10-06 07:17:15 -07:00
end
--state 0 = stats not loaded yet
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
watchers [ name ] = { } ; -- for spectator mod
2018-02-17 20:44:46 -08:00
2016-09-12 06:38:02 -07:00
local ip = tostring ( minetest.get_player_ip ( name ) ) ;
local msg = " " ;
2018-02-17 20:44:46 -08:00
2016-09-12 06:38:02 -07:00
-- check anticheat db
--check ip
2018-02-17 20:44:46 -08:00
if anticheatdb [ ip ] then
2016-09-12 06:38:02 -07:00
msg = " #anticheat: welcome back detected cheater, ip = " .. ip .. " , name " .. anticheatdb [ ip ] . name .. " , new name = " .. name ;
end ;
--check names
for ip , v in pairs ( anticheatdb ) do
2018-02-17 20:44:46 -08:00
if v.name == name then
2016-09-12 06:38:02 -07:00
msg = " #anticheat: welcome back detected cheater, ip = " .. ip .. " , name = newname = " .. v.name ;
break ;
end
end
2018-02-17 20:44:46 -08:00
2016-09-12 06:38:02 -07:00
if msg ~= " " then
2018-02-17 20:44:46 -08:00
for name , _ in pairs ( cheat.moderators ) do
2016-09-12 06:38:02 -07:00
minetest.chat_send_player ( name , msg ) ;
end
end
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
end )
2018-02-17 20:44:46 -08:00
minetest.register_chatcommand ( " cchk " , {
2016-08-29 00:20:26 -07:00
privs = {
interact = true
} ,
2017-08-01 23:37:15 -07:00
description = " cchk NAME, checks if player is cheating in this moment " ,
2016-08-29 00:20:26 -07:00
func = function ( name , param )
local privs = minetest.get_player_privs ( name ) . privs ;
if not cheat.moderators [ name ] and not privs then return end
2016-09-12 06:38:02 -07:00
local player = minetest.get_player_by_name ( param ) ;
if not player then return end
check_player ( player ) ;
2018-02-17 20:44:46 -08:00
local players = minetest.get_connected_players ( ) ;
2016-09-12 06:38:02 -07:00
for name , _ in pairs ( cheat.debuglist ) do -- show suspects in debug
for _ , player in pairs ( players ) do
local pname = player : get_player_name ( ) ;
if cheat.players [ pname ] . count > 0 then
minetest.chat_send_player ( name , " name " .. pname .. " , cheat pos " .. minetest.pos_to_string ( cheat.players [ pname ] . cheatpos ) .. " last clear pos " .. minetest.pos_to_string ( cheat.players [ pname ] . clearpos ) .. " cheat type " .. cheat.players [ pname ] . cheattype .. " cheatcount " .. cheat.players [ pname ] . count ) ;
end
end
end
2018-02-17 20:44:46 -08:00
2016-09-12 06:38:02 -07:00
end
} )
minetest.register_chatcommand ( " crep " , { -- see cheat report
privs = {
interact = true
} ,
2017-08-01 23:37:15 -07:00
description = " crep 0/1, 0 = default cheat report, 1 = connected player stats " ,
2016-09-12 06:38:02 -07:00
func = function ( name , param )
local privs = minetest.get_player_privs ( name ) . privs ;
if not cheat.moderators [ name ] and not privs then return end
2018-02-17 20:44:46 -08:00
if param == " " then
2016-10-06 07:20:53 -07:00
minetest.chat_send_player ( name , " use: crep type, types: 0(default) cheat report, 1 connected player stats ( " .. version .. " ) " ) ;
2016-09-12 06:38:02 -07:00
end
2018-02-17 20:44:46 -08:00
2016-09-14 02:42:06 -07:00
param = tonumber ( param ) or 0 ;
2018-02-17 20:44:46 -08:00
2016-09-14 02:42:06 -07:00
if param == 0 then -- show cheat report
local text = " " ;
for ip , v in pairs ( anticheatdb ) do
if v and v.name and v.msg then
2016-09-20 04:53:49 -07:00
text = text .. " ip " .. ip .. " " .. v.msg .. " \n " ;
2016-09-14 02:42:06 -07:00
end
end
if text ~= " " then
local form = " size [6,7] textarea[0,0;6.5,8.5;creport;CHEAT REPORT; " .. text .. " ] "
minetest.show_formspec ( name , " anticheatreport " , form )
end
elseif param == 1 then -- show cheat stats
local text = " " ;
local players = minetest.get_connected_players ( ) ;
for _ , player in pairs ( players ) do
local pname = player : get_player_name ( ) ;
2016-10-06 07:17:15 -07:00
local ip = tostring ( minetest.get_player_ip ( pname ) ) ;
2018-02-17 20:44:46 -08:00
2016-10-07 06:13:22 -07:00
text = text .. " \n name " .. pname .. " , digxp " .. math.floor ( 1000 * cheat.players [ pname ] . stats.digxp ) / 1000 ..
" , deltadigxp(2min) " .. math.floor ( 1000 * cheat.players [ pname ] . stats.deltadig ) / 1000 .. " , maxdeltadigxp " .. math.floor ( 1000 * cheat.players [ pname ] . stats.maxdeltadig ) / 1000 ; -- .. " ".. string.gsub(dump(cheat.players[pname].stats), "\n", " ");
2016-10-06 07:17:15 -07:00
if anticheatdb [ ip ] then text = text .. " (DETECTED) ip " .. ip .. " , name " .. anticheatdb [ ip ] . name end
2016-09-14 02:42:06 -07:00
end
if text ~= " " then
local form = " size [10,8] textarea[0,0;10.5,9.;creport;CHEAT STATISTICS; " .. text .. " ] "
minetest.show_formspec ( name , " anticheatreport " , form )
end
end
2016-09-12 06:38:02 -07:00
-- suspects info
local players = minetest.get_connected_players ( ) ;
2016-08-29 00:20:26 -07:00
for _ , player in pairs ( players ) do
local pname = player : get_player_name ( ) ;
if cheat.players [ pname ] . count > 0 then
minetest.chat_send_player ( name , " name " .. pname .. " , cheat pos " .. minetest.pos_to_string ( cheat.players [ pname ] . cheatpos ) .. " last clear pos " .. minetest.pos_to_string ( cheat.players [ pname ] . lastpos ) .. " cheat type " .. cheat.players [ pname ] . cheattype .. " cheatcount " .. cheat.players [ pname ] . count ) ;
end
end
2016-09-12 06:38:02 -07:00
2016-08-29 00:20:26 -07:00
end
} )
minetest.register_chatcommand ( " cdebug " , { -- toggle cdebug= display of stats on/off for this player
privs = {
interact = true
} ,
func = function ( name , param )
local privs = minetest.get_player_privs ( name ) . privs ;
if not cheat.moderators [ name ] and not privs then return end
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
if cheat.debuglist [ name ] == nil then cheat.debuglist [ name ] = true else cheat.debuglist [ name ] = nil end ;
2018-02-17 20:44:46 -08:00
2016-08-29 00:20:26 -07:00
minetest.chat_send_player ( name , " #anticheat: " .. version ) ;
2018-02-17 20:44:46 -08:00
if cheat.debuglist [ name ] == true then
2016-08-29 00:20:26 -07:00
minetest.chat_send_player ( name , " #anticheat: display of debug messages is ON " ) ;
else
minetest.chat_send_player ( name , " #anticheat: display of debug messages is OFF " ) ;
end
end
} )
2016-09-29 14:02:21 -07:00
2016-08-29 00:20:26 -07:00
------------------------------------------------------
-- [Mod] Spectator Mode [git] [spectator_mode]
-- https://github.com/minetest-mods/spectator_mode
------------------------------------------------------
local original_pos = { }
2018-01-01 21:26:20 -08:00
minetest.register_privilege ( " watch " , " Player can watch other players " )
2016-08-29 00:20:26 -07:00
2018-01-01 21:26:20 -08:00
local function toggle_hud_flags ( player , bool )
local flags = player : hud_get_flags ( )
local new_hud_flags = { }
2016-08-29 00:20:26 -07:00
2018-01-01 21:26:20 -08:00
for flag in pairs ( flags ) do
new_hud_flags [ flag ] = bool
end
2016-09-12 06:38:02 -07:00
2018-01-01 21:26:20 -08:00
player : hud_set_flags ( new_hud_flags )
end
2016-08-29 00:20:26 -07:00
2018-01-01 21:26:20 -08:00
local function unwatching ( name )
local watcher = minetest.get_player_by_name ( name )
local privs = minetest.get_player_privs ( name )
if watcher and default.player_attached [ name ] == true then
watcher : set_detach ( )
default.player_attached [ name ] = false
watcher : set_eye_offset ( vector.new ( ) , vector.new ( ) )
watcher : set_nametag_attributes ( { color = { a = 255 , r = 255 , g = 255 , b = 255 } } )
toggle_hud_flags ( watcher , true )
watcher : set_properties ( {
visual_size = { x = 1 , y = 1 } ,
makes_footstep_sound = true ,
collisionbox = { - 0.3 , - 1 , - 0.3 , 0.3 , 1 , 0.3 }
} )
if not privs.interact and privs.watch == true then
privs.interact = true
minetest.set_player_privs ( name , privs )
end
local pos = original_pos [ watcher ]
if pos then
-- setpos seems to be very unreliable
-- this workaround helps though
minetest.after ( 0.1 , function ( )
watcher : setpos ( pos )
end )
original_pos [ watcher ] = nil
end
end
end
minetest.register_chatcommand ( " watch " , {
params = " <to_name> " ,
description = " watch a given player " ,
privs = { watch = true } ,
func = function ( name , param )
local watcher = minetest.get_player_by_name ( name )
local target = minetest.get_player_by_name ( param )
local privs = minetest.get_player_privs ( name )
if target and watcher ~= target then
if default.player_attached [ name ] == true then
unwatching ( param )
else
original_pos [ watcher ] = watcher : getpos ( )
end
2018-01-01 21:36:57 -08:00
-- show inventory
local tinv = target : get_inventory ( ) : get_list ( " main " ) ;
for i , v in pairs ( tinv ) do tinv [ i ] = v : to_string ( ) ; end
tinv = dump ( tinv ) ;
local form = " size [6,7] textarea[0,0;6.5,8.5;creport;INVENTORY LIST; " .. minetest.formspec_escape ( tinv ) .. " ] "
minetest.show_formspec ( name , " watch_inventory " , form )
2018-01-01 21:26:20 -08:00
default.player_attached [ name ] = true
watcher : set_attach ( target , " " , vector.new ( 0 , - 5 , - 20 ) , vector.new ( ) )
watcher : set_eye_offset ( vector.new ( 0 , - 5 , - 20 ) , vector.new ( ) )
watcher : set_nametag_attributes ( { color = { a = 0 } } )
toggle_hud_flags ( watcher , true )
watcher : set_properties ( {
visual_size = { x = 0 , y = 0 } ,
makes_footstep_sound = false ,
collisionbox = { 0 }
} )
privs.interact = nil
minetest.set_player_privs ( name , privs )
return true , " Watching ' " .. param .. " ' at " ..
minetest.pos_to_string ( vector.round ( target : getpos ( ) ) )
end
return false , " Invalid parameter (' " .. param .. " '). "
end
2016-08-29 00:20:26 -07:00
} )
minetest.register_chatcommand ( " unwatch " , {
2018-01-01 21:26:20 -08:00
description = " unwatch a player " ,
privs = { watch = true } ,
func = function ( name , param )
unwatching ( name )
end
2016-08-29 00:20:26 -07:00
} )
minetest.register_on_leaveplayer ( function ( player )
2018-01-01 21:26:20 -08:00
local name = player : get_player_name ( )
unwatching ( name )
2016-08-29 00:20:26 -07:00
end )