implement basic anticheat and admin commands : anticheat + beowulf + fairplay

* TODO: check for fairplay client detection and noclip
* implements the beowulf noclip detection and punishmend
* implements the antifly and antifast detection
* autodetect if antichat or beowulf are presents
  so dont override functions
master
mckaygerhard 2023-07-17 04:02:10 -04:00
parent 0c4039dd1c
commit 2942ce1b50
6 changed files with 646 additions and 12 deletions

View File

@ -24,11 +24,14 @@ Repository : https://git.minetest.io/minenux/minetest-mod-governing
#### Configurations
| config param | value | req | default | example |
| --------------------- | ---------- | --- | -------- | -------------------------------- |
| secure.http_mods | governing | yes | none set | geoip,governing |
| secure.trusted_mods | governing | yes | none set | auth_rx,governing |
| governing.checkapikey | `<string>` | yes | none set | 177cd4797a4949c4b1114d2e4ab9af11 |
| config param | type | value | req | default/min/mx | example or description |
| --------------------- | ------ | ---------- | --- | --------------- | -------------------------------- |
| secure.http_mods | string | governing | yes | none set | geoip,governing |
| secure.trusted_mods | string | governing | yes | none set | auth_rx,governing |
| governing.checkapikey | string | `<string>` | yes | none set | 177cd4797a4949c4b1114d2e4ab9af11 |
| anticheat.timestep | int | 15 | no | 15 / 10 / 300 | How many time will run checks in seconds |
| anticheat.timeagain | int | 15 | no | 15 / 10 / 300 | How many seconds checks again (to compare) on a suspected player |
| anticheat.moderators | string | admin | yes | admin,singleplayer | Comma separated list name players that can watch and check stats |
The `governing.checkapikey` string is requested by registering a free (or paid)
account at https://vpnapi.io/ and **if not configured, will be used a simple geoip request**.
@ -36,6 +39,8 @@ account at https://vpnapi.io/ and **if not configured, will be used a simple geo
1. its powered by [VPNapi.io IP VPN & Proxy Detection API](https://vpnapi.io/api-documentation)
2. but if apykey is not set, its powered by [IP Location Finder by KeyCDN](https://tools.keycdn.com/geo)
> Warning: anticheat its a WIP from rnd's anticheat mod with beowulf mod fusioned
#### Commands
| command & format | permission | description function | observations |
@ -43,15 +48,20 @@ account at https://vpnapi.io/ and **if not configured, will be used a simple geo
| `/killme` | interact | kill yourselft | no matter if killme mod is present or not |
| `/govip <playername>` | governing | complete ip player info | Will require keyapi configuration set |
| `/geoip <playername>` | geoip | simple ip player info | no matter if geoip mod is present or not |
| `/cstats` | moderator | to see latest detected cheater | its not a privilegie, use config settings |
| `/cdebug` | moderator | to see even suspected cheats to be verified later | its not a privilegie, use config settings |
#### privs
* `geoip` can make a geoip query, no matter if geoip mod is present or missing
* `governing` can run administration command like ip check
> Warning: currently moderator its not a privs.. its a list of players names, its a WIP from rnd's anticheat mod with beowulf mod fusioned
#### Api
Currently only geoip functions are exposed, we later exposed antcheat also
```lua
-- lookup command
governing.checkip("213.152.186.35", function(result)
@ -100,9 +110,10 @@ Check IP information on data:
## LICENSE
Copyright (C) 2023 mckaygerhard <mckaygerhard@gmail.com> CC-BY-SA-NC 4.0
Copyright (C) 2023 mckayshirou <mckayshirou@gmail.com> CC-BY-SA-NC 4.0
* Copyright (C) 2023 mckaygerhard <mckaygerhard@gmail.com> CC-BY-SA-NC 4.0
* Copyright (C) 2023 mckayshirou <mckayshirou@gmail.com> CC-BY-SA-NC 4.0
* Copyright 2016-2017 rnd LGPL v3 for anticheat code of minetest 0.4 engines
* Copyright 2021-2023 BuckarooBanzay MIT for beowulf code of mt-mods
It applies CC-BY-SA-NC 4.0 unless you ask to request permission
with special conditions. The ipcheck idea, the mod inclusion,

608
anticheats.lua Normal file
View File

@ -0,0 +1,608 @@
-------------------------------------------------------------------------
-- ANTI PRIVS detects if a custom client without interacting "zips" around the map
-- Copyright (c) 2021 mt-mods/BuckarooBanzay MIT
-- Copyright 2020-2023 improvements and few fixes, mckaygerhard CC-BY-SA-NC 4.0
-------------------------------------------------------------------------
-- per-player data
local player_data = {}
-- obtain player data cheats, improved respect beowulf with missing checks and methods
local function get_player_data(name)
local player = minetest.get_player_by_name(name)
if not player then return nil end
if not player_data[name] then
player_data[name] = {
fliyers = 0, -- number of checks for fly ( added only with governing)
strikes = 0, -- number of "strikes" (odd movements)
checked = 0, -- number of checks
pre_pos = player:get_pos() -- position track (missing in beowulf as bug)
}
end
return player_data[name]
-- WARNING pos its always current so checked must be after an interval using callbacks
end
-- clear player data for cheats
local function track_player_clear(player)
if player then
if player:get_player_name() then
player_data[player:get_player_name()] = nil
end
end
end
-- store player data cheats strikes and checks
local function track_player(player)
local name = player:get_player_name()
local data = get_player_data(name)
local pos = player:get_pos()
if data.pre_pos then
-- compare positions
local d = vector.distance(pos, data.pre_pos)
if d > 200 then
data.strikes = data.strikes + 1
end
data.checked = data.checked + 1
end
if data.checked >= 10 then
-- check strike-count after 10 movement checks
if data.strikes > 8 then
-- suspicious movement, log it
-- TODO: if this doesn't yield any false-positives, add a kick/ban option
local msg = "suspicious movement detected for player: '" .. name .. "'"
minetest.log("action", "[governing][beowulf] " .. msg)
end
-- reset counters
data.checked = 0
data.strikes = 0
end
-- store current position
data.pre_pos = pos
end
-------------------------------------------------------------------------
-- ANTI CHEAT for MT4 and old MT5 engines by rnd
-- Copyright 2016 rnd LGPL v3
-- Copyright 2020-2023 improvements and few fixes, mckaygerhard CC-BY-SA-NC 4.0
-------------------------------------------------------------------------
local cheat = {};
local version = "09/08/2017";
anticheatsettings = {};
anticheatsettings.moderators = {}
anticheatsettings.CHEAT_TIMESTEP = tonumber(minetest.settings:get("governing.timestep")) or 15; -- check timestep all players
anticheatsettings.CHECK_AGAIN = tonumber(minetest.settings:get("governing.timeagain")) or 15; -- after player found in bad position check again after this to make sure its not lag, this should be larger than expected lag in seconds
anticheatsettings.STRING_MODERA = minetest.settings:get("governing.moderators") or "admin,singleplayer";
-- moderators list, those players can use cheat debug and will see full cheat message
for str in string.gmatch(anticheatsettings.STRING_MODERA, "([^,]+)") do table.insert(anticheatsettings.moderators, str) end
local CHEAT_TIMESTEP = anticheatsettings.CHEAT_TIMESTEP;
local CHECK_AGAIN = anticheatsettings.CHECK_AGAIN;
cheat.moderators = anticheatsettings.moderators;
bonemod = minetest.get_modpath("boneworld")
anticheatdb = {}; -- data about detected cheaters
cheat.suspect = "";
cheat.players = {}; -- temporary cheat detection db
cheat.message = "";
cheat.debuglist = {}; -- [name]=true -- who gets to see debug msgs
cheat.scan_timer = 0; -- global scan of players
cheat.stat_timer = 0; -- used to collect stats
cheat.nodelist = {};
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)
local player = minetest.get_player_by_name(name);
local ip = tostring(minetest.get_player_ip(name));
if not player then return end
local text=""; local logtext = "";
if cheat.players[name].cheattype == 1 then
text = "#anticheat: ".. name .. " was caught walking inside wall";
logtext = os.date("%H:%M.%S").." #anticheat: ".. name .. " was caught walking inside wall at " .. minetest.pos_to_string(cheat.players[name].cheatpos);
player:set_hp(0);
elseif cheat.players[name].cheattype == 2 then
local gravity = player:get_physics_override().gravity; if gravity<1 then return end
logtext= os.date("%H:%M.%S").." #anticheat: ".. name .. " was caught flying at " .. minetest.pos_to_string(cheat.players[name].cheatpos);
if cheat.players[name].cheatpos.y>5 then -- only above height 5 it directly damages flyer
text = "#anticheat: ".. name .. " was caught flying";
--player:set_hp(0);
end
end
if text~="" then
minetest.chat_send_all(text);
end
if logtext~="" then
minetest.log("action", logtext);
cheat.message = logtext;
anticheatdb[ip] = {name = name, msg = logtext};
cheat.players[name].count=0; -- reset counter
cheat.players[name].cheattype = 0;
for name,_ in pairs(cheat.moderators) do -- display full message to moderators
minetest.chat_send_player(name,logtext);
end
end
end
-- CHECKS
-- DETAILED NOCLIP CHECK
local check_noclip = function(pos)
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
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;
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
return true
end
end
local round = function (x)
if x > 0 then
return math.floor(x+0.5)
else
return -math.floor(-x+0.5)
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
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
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
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
-- 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
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
if cheat.players[name].count == 0 then -- mark as suspect
cheat.players[name].count = 1;
cheat.players[name].cheattype = 1;
end
end
end
-- 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
if fly then -- so we are in air, are we flying?
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
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
if fly then -- so now in air above previous position, which was in air too?
if cheat.players[name].count == 0 then cheat.players[name].cheatpos = pos end -- remember first position where player found "cheating"
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
cheat.players[name].count = 0;
cheat.players[name].cheattype = 0;
end
end
)
end
if cheat.players[name].count == 0 then -- mark as suspect
cheat.players[name].count = 1;
cheat.players[name].cheattype = 2;
end
end
end
end
end
cheat.players[name].lastpos = pos
end
minetest.register_globalstep(function(dtime)
cheat.scan_timer = cheat.scan_timer + dtime
-- GENERAL SCAN OF ALL PLAYERS
if cheat.scan_timer>cheat.timestep then
cheat.stat_timer = cheat.stat_timer + cheat.timestep;
-- dig xp stats every 2 minutes
if bonemod 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
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;
if deltadig>cheat.players[pname].stats.maxdeltadig then
cheat.players[pname].stats.maxdeltadig = deltadig;
end
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
end
end
end
end
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();
for _,player in pairs(players) do
check_player(player);
end
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
end
end)
-- long range dig check
local check_can_dig = function(pos, digger)
local p = digger:getpos();
if p.y<0 then p.y=p.y+2 else p.y=p.y+1 end -- head position
local dist = math.max(math.abs(p.x-pos.x),math.abs(p.y-pos.y),math.abs(p.z-pos.z));
if dist>6 then -- here 5
dist = math.floor(dist*100)/100;
local pname = digger:get_player_name();
local logtext = os.date("%H:%M.%S") .. "#anticheat: long range dig " .. pname ..", distance " .. dist .. ", pos " .. minetest.pos_to_string(pos);
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
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
minetest.register_on_joinplayer(function(player) -- init stuff on player join
local name = player:get_player_name(); if name == nil then return end
local pos = player:getpos();
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
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
minetest.after(5, -- load digxp after boneworld loads it
function()
if bonemod then
if boneworld.xp then
cheat.players[name].stats.digxp = boneworld.digxp[name] or 0;
cheat.players[name].stats.state = 1;
end
end
end
)
end
--state 0 = stats not loaded yet
local ip = tostring(minetest.get_player_ip(name));
local msg = "";
-- check anticheat db
--check ip
if anticheatdb[ip] then
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
if v.name == name then
msg = "#anticheat: welcome back detected cheater, ip = " .. ip .. ", name = newname = " .. v.name;
break;
end
end
if msg~="" then
for name,_ in pairs(cheat.moderators) do
minetest.chat_send_player(name,msg);
end
end
end)
minetest.register_chatcommand("cchk", {
privs = {
interact = true
},
description = "cchk NAME, checks if player is cheating in this moment",
func = function(name, param)
local privs = minetest.get_player_privs(name).privs;
if not cheat.moderators[name] and not privs then return end
local player = minetest.get_player_by_name(param);
if not player then return end
check_player(player);
local players = minetest.get_connected_players();
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
end
})
minetest.register_chatcommand("crep", { -- see cheat report
privs = {
interact = true
},
description = "crep 0/1, 0 = default cheat report, 1 = connected player stats",
func = function(name, param)
local privs = minetest.get_player_privs(name).privs;
if not cheat.moderators[name] and not privs then return end
if param == "" then
minetest.chat_send_player(name,"use: crep type, types: 0(default) cheat report, 1 connected player stats (".. version ..")");
end
param = tonumber(param) or 0;
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
text = text .. "ip " .. ip .. " ".. v.msg .. "\n";
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();
local ip = tostring(minetest.get_player_ip(pname));
text = text .. "\nname " .. 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", " ");
if anticheatdb[ip] then text = text .. " (DETECTED) ip ".. ip .. ", name " .. anticheatdb[ip].name end
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
-- suspects info
local players = minetest.get_connected_players();
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
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
if cheat.debuglist[name] == nil then cheat.debuglist[name] = true else cheat.debuglist[name] = nil end;
minetest.chat_send_player(name,"#anticheat: " .. version);
if cheat.debuglist[name]==true then
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
})
-------------------------------------------------------------------------
-- GOVERNONR custom action for features mod code improvements
-- minetest routines to implement the mod features
-- Copyright 2020-2023 mckaygerhard CC-BY-SA-NC 4.0
-------------------------------------------------------------------------
-- cleanup after the player leaves
minetest.register_on_leaveplayer(function(player)
if player then
track_player_clear(player)
end
end)
-- list of nodes to enable damage if noclip or to check if player its diggin too fast
local node_list_check = {}
-- TODO move this to a comma separted list in config, and adde the check below in node check for fly
if minetest.get_modpath("default") then
table.insert(node_list_check, "default:stone")
table.insert(node_list_check, "default:stone_with_iron")
table.insert(node_list_check, "default:stone_with_copper")
table.insert(node_list_check, "default:stone_with_coal")
table.insert(node_list_check, "default:stone_with_gold")
table.insert(node_list_check, "default:stone_with_mese")
table.insert(node_list_check, "default:stone_with_diamond")
end
local function interval_fn()
if not governing.modbeowulf then
for _, player in ipairs(minetest.get_connected_players()) do
track_player(player)
end
end
if not governing.modanticheat then
for _, nodename in ipairs(node_list_check) do
set_check_can_dig(nodename);
end
end
minetest.after(2, interval_fn)
end
-- TODO: beowulf was pretty unneficient on large player servers.. cos only its made each 1 second and again each x inside function
if governing.is_50 then
minetest.register_on_mods_loaded(interval_fn )
else
minetest.after(0.1, interval_fn)
end
-- damaged if a player its making noclip, no matter if admin is doing
for _, nodename in ipairs(node_list_check) do
minetest.override_item(nodename, {
damage_per_second = 1
})
end

View File

@ -1,2 +1,3 @@
default?
mail?
boneworld?

View File

@ -24,6 +24,8 @@ local moddefault = minetest.get_modpath("default") -- path of default mod if ava
local modkillme = minetest.get_modpath("killme") -- if killme is present as mod if available, otherwise we use own
local modcommand = minetest.get_modpath("game_commands") -- updated killme is present as mod if available, otherwise we use own
local modcreative = minetest.get_modpath("creative") -- if creative is available, otherwise false/nil
local modanticheat = minetest.get_modpath("anticheat") -- if rnd1 anticheat mod is loaded, cos only wolrs with mt4
local modbeowulf = minetest.get_modpath("beowulf") -- if rnd1 anticheat mod is loaded, cos only wolrs with mt4
local checkapikey = minetest.settings:get("governing.checkapikey") or "" -- need for geoip improved ip information, then geoip normal will be used
local is_46 = minetest.has_feature("add_entity_with_staticdata") -- detect minetest engine 4.0.16 or mayor
@ -90,6 +92,8 @@ governing.modmail = modmail -- path of the mail mod if available for sending mes
governing.moddefault = moddefault -- path of default mod if available, otherwise false/nil
governing.modkillme = modkillme -- if killme is present as mod if available, otherwise we use own
governing.modcommand = modcommand -- if same killme and commands is present as mod if available, otherwise we use own
governing.modanticheat = modanticheat -- if rnd1 anticheat mod is present cos only works with mt4
governing.modbeowulf = modbeowulf -- if beowulf anticheat mod is present cos only works with mt5
governing.checkapikey = checkapikey -- need for geoip improved ip information, then geoip normal will be used
--[[ end - namespace of the mod and varible usages ]]
@ -168,16 +172,23 @@ end
--[[ include code files ]]
--[[ include code files mod features ]]
dofile(governing.modpath.."/geoip.lua") -- format the geoip responses managed by "callback(data)"
dofile(governing.modpath.."/anticheats.lua") -- load anticheat mod that combined rnd + beowulf + fairplay
dofile(governing.modpath.."/commands.lua") -- must be at end. so relies on others functionalities
--[[ end of include code files mod features ]]
--[[ log files and finished of mod loading ]]
if governing.checkapikey == "" then minetest.log("warning", "[goberning] governing.checkapikey not set or empty") end
if governing.checkapikey == "" then minetest.log("warning", "[goberning] governing.checkapikey not set or empty, only basic geoip will work") end
print("[MOD] governing mod loaded" )

View File

@ -1,2 +1,2 @@
name = governing
optional_depends = default, mail
optional_depends = default, mail, boneworld

View File

@ -1 +1,4 @@
governing.checkapikey (The string need for use the https://vpnapi.io/ ip informacion with geolocation) string
governing.timestep (How many time will run checks in seconds) int 15 10 300
governing.timeagain (How many seconds checks again to compare if it is cheating the suspected player) int 120 10 300
governing.moderators (Comma separated list of name players that will be not checked, without spaces) string