diff --git a/README.md b/README.md index 2e24dd0..2c0ac6f 100644 --- a/README.md +++ b/README.md @@ -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 | `` | 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 | `` | 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 ` | governing | complete ip player info | Will require keyapi configuration set | | `/geoip ` | 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 CC-BY-SA-NC 4.0 - -Copyright (C) 2023 mckayshirou CC-BY-SA-NC 4.0 +* Copyright (C) 2023 mckaygerhard CC-BY-SA-NC 4.0 +* Copyright (C) 2023 mckayshirou 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, diff --git a/anticheats.lua b/anticheats.lua new file mode 100644 index 0000000..5598485 --- /dev/null +++ b/anticheats.lua @@ -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 + + diff --git a/depends.txt b/depends.txt index 3e00327..bb2b93d 100644 --- a/depends.txt +++ b/depends.txt @@ -1,2 +1,3 @@ default? mail? +boneworld? diff --git a/init.lua b/init.lua index 50fdc87..dc1e98f 100644 --- a/init.lua +++ b/init.lua @@ -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" ) diff --git a/mod.conf b/mod.conf index 037313b..37af350 100644 --- a/mod.conf +++ b/mod.conf @@ -1,2 +1,2 @@ name = governing -optional_depends = default, mail +optional_depends = default, mail, boneworld diff --git a/settingtypes.txt b/settingtypes.txt index ddf0625..3a997a9 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -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