commit 28f0114b7b04b5ad7cb05a8936116e9a0ce7fa5b Author: Evert Prants Date: Sun Nov 10 22:12:29 2019 +0200 Essentials core, repair, tpa, back, top, spawn, setspawn diff --git a/ess/init.lua b/ess/init.lua new file mode 100644 index 0000000..aafd569 --- /dev/null +++ b/ess/init.lua @@ -0,0 +1,15 @@ + +local modpath = minetest.get_modpath(minetest.get_current_modname()) + +-- Register base module +ess.register_module("ess", "Essential commands for server management") + +-- Tools related commands +dofile(modpath.."/tools.lua") + +-- Teleportation related commands +dofile(modpath.."/teleport.lua") +dofile(modpath.."/warp.lua") + +-- Player related commands +dofile(modpath.."/player.lua") diff --git a/ess/mod.conf b/ess/mod.conf new file mode 100644 index 0000000..203e8b0 --- /dev/null +++ b/ess/mod.conf @@ -0,0 +1,3 @@ +name=ess +description=Essential commands for server management +depends=ess_core diff --git a/ess/player.lua b/ess/player.lua new file mode 100644 index 0000000..e69de29 diff --git a/ess/teleport.lua b/ess/teleport.lua new file mode 100644 index 0000000..c3c76fa --- /dev/null +++ b/ess/teleport.lua @@ -0,0 +1,195 @@ + +local tpask = { + requests = {}, + muted = {}, + timeout = 120, +} + +local function cmd_top(name) + local curr_pos = minetest.get_player_by_name(name):getpos() + curr_pos.y = math.ceil(curr_pos.y) + 0.5 + + while minetest.get_node(curr_pos).name ~= "ignore" do + curr_pos.y = curr_pos.y + 1 + end + + curr_pos.y = curr_pos.y - 0.5 + + while minetest.get_node(curr_pos).name == "air" do + curr_pos.y = curr_pos.y - 1 + end + curr_pos.y = curr_pos.y + 0.5 + + minetest.get_player_by_name(name):set_pos(curr_pos) + return true, "Teleported to top." +end + +local function cmd_back(name) + local pos = minetest.string_to_pos(ess.get_player_meta(name, "position")) + if not pos then + return false, "Could not return to previous position." + end + minetest.get_player_by_name(name):set_pos(pos) + return true, "Teleported back." +end + +local function cmd_tpcommon(name,tname,direction) + if name == tname then + return false, "Cannot teleport to self." + end + + local target = minetest.get_player_by_name(tname) + if not target then + return false, "Could not find player." + end + + if tpask.requests[tname] and tpask.requests[tname].when > minetest.get_us_time() - tpask.timeout * 100000 then + return false, "There are currently pending requests regarding this player. Please wait." + end + + if not tpask.muted[tname] then + local message = "to teleport to you" + + if direction == 2 then + message = "you to teleport to them" + end + + minetest.chat_send_player(tname, name .. " has requested "..message..".") + minetest.chat_send_player(tname, "Run /tpaccept to accept or /tpadeny to deny.") + if tpask.timeout > 0 then + minetest.chat_send_player(tname, "You have "..tpask.timeout.." seconds to respond.") + end + tpask.requests[tname] = {when = minetest.get_us_time()} + if direction == 1 then + tpask.requests[tname].who = name + else + tpask.requests[tname].to = name + end + end + + return true, "Teleport request sent to " .. tname .. ".." +end + +local function cmd_tpask(name,tname) + return cmd_tpcommon(name,tname,1) +end + +local function cmd_tpaskhere(name,tname) + return cmd_tpcommon(name,tname,2) +end + +local function cmd_tpaconfirm(name) + local reqs = tpask.requests[name] + local me = minetest.get_player_by_name(name) + if not reqs or (tpask.timeout > 0 and tpask.requests[name].when < minetest.get_us_time() - tpask.timeout * 100000) then + return false, "You have no pending teleport requests." + end + + local who = reqs.to or reqs.who + local whoplayer = minetest.get_player_by_name(who) + if not whoplayer then + tpask.requests[name] = nil + return false, "You have no pending teleport requests." + end + + if reqs.to then + minetest.chat_send_player(name, "Teleporting..") + me:set_pos(whoplayer:get_pos()) + else + minetest.chat_send_player(who, "Teleporting..") + whoplayer:set_pos(me:get_pos()) + end + + tpask.requests[name] = nil + + return true +end + +local function cmd_tpadeny(name) + local reqs = tpask.requests[name] + if not reqs or tpask.requests[name].when < minetest.get_us_time() - tpask.timeout * 1000 then + return false, "You have no pending teleport requests." + end + + tpask.requests[name] = nil + + return true, "Declined teleport request successfully. Use /tpmute to silence teleport requests." +end + +local function cmd_tpamute(name) + if tpask.muted[name] then + tpask.muted[name] = nil + return true, "Unmuted teleport requests successfully." + end + + tpask.muted[name] = "*" + return true, "Muted teleport requests successfully." +end + +-- Use builtin tele +local cmd_teleport = minetest.registered_chatcommands["teleport"].func + +local commands = { + ["tpask"] = { + description = "Request to teleport to the specified player.", + params = "", + aliases = {"tpa"}, + privs = { + ["ess.teleport.tpa"] = true, + }, + save_player_pos = true, + func = cmd_tpask, + }, + ["tpaskhere"] = { + description = "Request to teleport the specified player to you.", + params = "", + aliases = {"tpahere"}, + privs = { + ["ess.teleport.tpahere"] = true, + }, + save_player_pos = true, + func = cmd_tpaskhere, + }, + ["tpaccept"] = { + description = "Accept a teleport request.", + aliases = {"tpayes"}, + func = cmd_tpaconfirm, + save_player_pos = true, + }, + ["tpdeny"] = { + description = "Reject a teleport request.", + aliases = {"tpno"}, + func = cmd_tpadeny, + }, + ["tpmute"] = { + description = "Silence/unsilence teleport requests.", + func = cmd_tpamute, + }, + ["back"] = { + description = "Teleports you to your location prior to tp/spawn/warp.", + aliases = {"return"}, + privs = true, + save_player_pos = true, + func = cmd_back, + }, + ["top"] = { + description = "Teleport to the highest node at your current position.", + privs = true, + save_player_pos = true, + func = cmd_top, + }, + ["teleport"] = { + description = "Teleport to position or player", + aliases = {"tp", "tele", "tp2p"}, + params = ",, | | ( ,,) | ( )", + privs = { + ["teleport"] = true, + ["ess.teleport"] = true, + }, + override = true, + save_player_pos = true, + func = cmd_teleport, + } +} + +ess.autoregister(commands, "teleport") diff --git a/ess/tools.lua b/ess/tools.lua new file mode 100644 index 0000000..15b9c60 --- /dev/null +++ b/ess/tools.lua @@ -0,0 +1,73 @@ + +-- Repair command +local function cmd_repair(name, params, splitparams) + local player = minetest.get_player_by_name(name) + local commit = 0 + + if splitparams[1] == name and splitparams[2] == "all" then + splitparams[1] = "all" + end + + if splitparams[1] == "all" then + if not ess.priv_match(name, "ess.tools.repairall") then + return ess.reject_permission() + end + -- Repair all tools + commit = 2 + elseif splitparams[2] == "all" and minetest.get_player_by_name(splitparams[1]) then + if not ess.priv_match(name, "ess.tools.repairall.other") then + return ess.reject_permission() + end + player = minetest.get_player_by_name(splitparams[1]) + -- Repair all of a player's tools + commit = 2 + elseif splitparams[1] ~= name and minetest.get_player_by_name(splitparams[1]) then + if not ess.priv_match(name, "ess.tools.repair.other") then + return ess.reject_permission() + end + player = minetest.get_player_by_name(splitparams[1]) + -- Repair a player's held tool + commit = 1 + else + -- Repair my own tool + commit = 1 + end + + if commit == 1 and player then + local held = player:get_wielded_item() + if held:get_wear() > 0 then + held:set_wear(0) + end + player:set_wielded_item(held) + return true, "Successfully repaired the held tool!" + elseif commit == 2 and player then + local inv = player:get_inventory() + local list = inv:get_list("main") + for _,stack in pairs(list) do + if stack:get_wear() > 0 then + stack:set_wear(0) + end + end + inv:set_list("main", list) + return true, "Successfully repaired all tools!" + end + + return false,"Invalid parameters." +end + +local commands = { + ["repair"] = { + description = "Repair currently held tool.", + aliases = {"fix"}, + privs = { + ["ess.tools.repair"] = true, + ["ess.tools.repair.other"] = true, + ["ess.tools.repairall"] = true, + ["ess.tools.repairall.other"] = true, + }, + params = "[] [all]", + func = cmd_repair + } +} + +ess.autoregister(commands, "tools") diff --git a/ess/warp.lua b/ess/warp.lua new file mode 100644 index 0000000..0d45b95 --- /dev/null +++ b/ess/warp.lua @@ -0,0 +1,3 @@ +local commands = {} + +ess.autoregister(commands, "warp") diff --git a/ess_chat/init.lua b/ess_chat/init.lua new file mode 100644 index 0000000..a984804 --- /dev/null +++ b/ess_chat/init.lua @@ -0,0 +1,5 @@ + +local modpath = minetest.get_modpath(minetest.get_current_modname()) + +-- Register base module +ess.register_module("ess_chat", "Chat overhaul library") diff --git a/ess_chat/mod.conf b/ess_chat/mod.conf new file mode 100644 index 0000000..b5817de --- /dev/null +++ b/ess_chat/mod.conf @@ -0,0 +1,3 @@ +name=ess_chat +description=Chat overhaul library +depends=ess_core diff --git a/ess_core/init.lua b/ess_core/init.lua new file mode 100644 index 0000000..bfd5d80 --- /dev/null +++ b/ess_core/init.lua @@ -0,0 +1,354 @@ +-- IcyEssentials Core Registration Framework + +local storage = minetest.get_mod_storage() + +ess = { + commands = {}, + privileges = {}, + modules = {}, + player_meta = {}, + world_meta = nil, +} + +--------------------- +-- PLAYER METADATA -- +--------------------- + +-- Load player metadata +local function playerdata_load(name) + local decoded = minetest.deserialize(storage:get_string(name)) + if not decoded then + ess.player_meta[name] = {} + return {} + end + + ess.player_meta[name] = decoded + return decoded +end + +-- Save player metadata +local function playerdata_save(name) + if not ess.player_meta[name] then return end + local encoded = minetest.serialize(ess.player_meta[name]) + storage:set_string(name, encoded) +end + +-- Set a player metadata value +function ess.set_player_meta(player, flag, value) + if not ess.player_meta[player] then + playerdata_load(player) + end + ess.player_meta[player][flag] = value + playerdata_save(player) +end + +-- Get a player's metadata value +function ess.get_player_meta(player, flag) + if not ess.player_meta[player] then + playerdata_load(player) + end + return ess.player_meta[player][flag] +end + +-------------------- +-- WORLD METADATA -- +-------------------- + +-- Load player metadata +local function worlddata_load() + local decoded = minetest.deserialize(storage:get_string("_data")) + if not decoded then + ess.world_meta = {} + return {} + end + + ess.world_meta = decoded + return decoded +end + +-- Save player metadata +local function worlddata_save() + if not ess.world_meta then return end + local encoded = minetest.serialize(ess.world_meta) + storage:set_string("_data", encoded) +end + +-- Set a player metadata value +function ess.set_world_meta(player, flag, value) + if not ess.world_meta then worlddata_load() end + ess.world_meta[flag] = value + worlddata_save() +end + +-- Get a player's metadata value +function ess.get_world_meta(flag) + if not ess.world_meta then worlddata_load() end + return ess.world_meta[flag] +end + +------------- +-- UTILITY -- +------------- + +-- Just return a (TODO: translated) string that rejects permission +function ess.reject_permission() + return false, "You don't have permission to run this command." +end + +-- Match a single privilege +function ess.priv_match(name, priv) + return minetest.check_player_privs(name, {[priv] = true}) +end + +-- Save a player's position for use with /back +local function save_player_pos(player, commit) + local pobj = minetest.get_player_by_name(player) + if not pobj then return end + + local pos = pobj:get_pos() + if commit then + ess.set_player_meta(player, "position", minetest.pos_to_string(commit)) + end + + return pos +end + +---------------- +-- PRIVILEGES -- +---------------- + +local function handle_command_privileges(privileges, description, default) + local perms = {} + for perm in pairs(privileges) do + local parts = string.split(perm, ".") + if ess.modules[parts[1]] and not ess.privileges[perm] then + minetest.register_privilege(perm, { + description = description, + give_to_singleplayer = default, + }) + ess.privileges[perm] = true + end + perms[perm] = true + + if #parts > 1 then + for i,p in ipairs(parts) do + if i == 1 then + local a = p .. ".all" + if not ess.privileges[a] then + minetest.register_privilege(a, { + description = "icyess all commands in module " .. p, + give_to_singleplayer = false, + give_to_admin = false + }) + ess.privileges[a] = true + end + perms[a] = true + elseif i == 2 then + local a = parts[1] .. "." .. p .. ".all" + if not ess.privileges[a] then + minetest.register_privilege(a, { + description = "icyess all commands in module " .. p .. " category "..p, + give_to_singleplayer = false, + give_to_admin = false + }) + ess.privileges[a] = true + end + perms[a] = true + end + end + end + end + perms["ess.all"] = true + return perms +end + +------------------ +-- CHATCOMMANDS -- +------------------ + +local function register_chatcommand(command, def) + if def.privs then + def.privs = handle_command_privileges(def.privs, def.description, def.default == true) + end + + local function fn (name, params) + local privileges_met = false + + -- Check for any of the privileges + if def.privs then + for priv in pairs(def.privs) do + if ess.priv_match(name, priv) then + privileges_met = true + break + end + end + else + privileges_met = true + end + + if not privileges_met then + return ess.reject_permission() + end + + -- If this command is a teleport, save the player position + local player_pos + if def.save_player_pos then + player_pos = save_player_pos(name) + end + + local splitparams = string.split(params, " ") + + -- Run the chat command function + local ret,mesg = def.func(name, params, splitparams) + + -- If we saved player position and the command succeeded, commit the last position save + if ret and player_pos then + save_player_pos(name, player_pos) + end + + return ret,mesg + end + + -- Clean-up the command definition + local pd = table.copy(def) + pd.privs = {} + pd.module = nil + pd.category = nil + pd.default = nil + pd.override = nil + pd.override_aliases = nil + pd.save_player_pos = nil + pd.func = fn + + -- If this command is overriding, check if a command like this already exists and override it + if def.override and minetest.registered_chatcommands[command] then + minetest.override_chatcommand(command, pd) + else + minetest.register_chatcommand(command, pd) + end +end + +--[[ + IcyEss Module registration + Registers a module. +]] +function ess.register_module(modname, description) + ess.modules[modname] = { mod = minetest.get_current_modname(), description = description } + return { + register_chatcommand = function (root, cmddef) + cmddef.module = modname + return ess.register_chatcommand(root, cmddef) + end + } +end + +--[[ + IcyEss Command registration + { + -- Command aliases (optional) + -- All of these will be registered as separate commands with the same + -- privileges and execute function + aliases = {}, + + -- Optional module name, such as "ess-chat" or "protect". + -- This will be used in privilege generation and grouping. + module = "ess", + + -- Optional command category, such as "time" or "item". + -- This will be used in privilege generation and grouping. + category = "item", + + -- Command privileges (optional) + -- Set to true to generate privilege based on the command name + privs = true, + + -- Set privileges a table in order to optionally require one of these privileges + -- If the privilege starts with the module name, they will automatically be + -- registered, if they don't already exist. + privs = { + "ess.item.repair" = true, + "ess.item.repair.other" = true, + }, + + -- Command execution (required) + func = function (name, params), + + -- If command like this exists, do we override it? + override = false, + + -- If aliases exist, do we override them? + override_aliases = false, + + -- If this command is given to singleplayer + default = false, + + -- If this command modifies player's position in some way, + -- save their current position before running the command + save_player_pos = false + } +]] +function ess.register_chatcommand(root, cmddef) + assert(type(cmddef) == "table", "command definition is not a table") + assert(cmddef.description ~= nil, "command is missing a description") + assert(cmddef.func ~= nil, "command definition is missing a function") + assert(type(cmddef.func) == "function", "command definition is missing a function") + + if ess.commands[root] and not cmddef.override then return end + if not cmddef.privs and cmddef.privileges then + cmddef.privs = table.copy(cmddef.privileges) + cmddef.privileges = nil + end + + local privs = cmddef.privs + if not privs then privs = {} end + + -- Set default module + if not cmddef.module then + cmddef.module = "ess" + end + + -- Generate privilege + if privs == true then + local a = "" + if cmddef.module then + a = a .. cmddef.module .. "." + end + + if cmddef.category then + a = a .. cmddef.category .. "." + end + + privs = {[a .. root] = true} + end + + -- No privileges required + local plen = 0 + for _ in pairs(privs) do + plen = plen + 1 + end + if plen == 0 then + privs = nil + end + + cmddef.privs = privs + ess.commands[root] = cmddef + register_chatcommand(root, cmddef) + + if not cmddef.aliases or #cmddef.aliases == 0 then return end + local aliasdef = table.copy(cmddef) + aliasdef.func = cmddef.func + aliasdef.override = cmddef.override_aliases == true + aliasdef.override_aliases = nil + aliasdef.description = cmddef.description .. " (alias to "..root..")" + + for _,a in pairs(cmddef.aliases) do + register_chatcommand(a,aliasdef) + end +end + +function ess.autoregister(list, category) + for cmd,def in pairs(list) do + def.category = category + ess.register_chatcommand(cmd, def) + end +end diff --git a/ess_core/mod.conf b/ess_core/mod.conf new file mode 100644 index 0000000..7598831 --- /dev/null +++ b/ess_core/mod.conf @@ -0,0 +1,2 @@ +name=ess_core +description=Command registration framework with better privileges diff --git a/ess_spawn/init.lua b/ess_spawn/init.lua new file mode 100644 index 0000000..8981926 --- /dev/null +++ b/ess_spawn/init.lua @@ -0,0 +1,42 @@ + +local modpath = minetest.get_modpath(minetest.get_current_modname()) + +-- Register base module +local spawn = ess.register_module("spawn", "Spawnpoint management") + +spawn.register_chatcommand("spawn", { + description = "Teleport to spawnpoint.", + privs = { + spawn = true, + ["spawn.all"] = true, + }, + save_player_pos = true, + override = true, + func = function (name) + local spawnpoint = minetest.setting_get_pos("static_spawnpoint") + if not spawnpoint then + return false, "There is no defined spawnpoint for this world." + end + minetest.get_player_by_name(name):set_pos(spawnpoint) + return true, "Teleported to spawn." + end +}) + +spawn.register_chatcommand("setspawn", { + description = "Set a spawnpoint for the world.", + privs = { + ["spawn.set"] = true, + ["spawn.all"] = true, + }, + override = true, + func = function (name) + if minetest.is_singleplayer() then + return false, "There is no point in setting a spawnpoint for a singleplayer world! Use /sethome instead." + end + local pos = minetest.get_player_by_name(name):get_pos(spawnpoint) + local str = minetest.pos_to_string(pos) + minetest.settings:set("static_spawnpoint", str) + minetest.settings:save() + return true, "Set the world's spawn point to " .. str + end +}) diff --git a/ess_spawn/mod.conf b/ess_spawn/mod.conf new file mode 100644 index 0000000..6b35ee9 --- /dev/null +++ b/ess_spawn/mod.conf @@ -0,0 +1,3 @@ +name=ess_spawn +description=Spawnpoint management +depends=ess_core diff --git a/modpack.conf b/modpack.conf new file mode 100644 index 0000000..7569914 --- /dev/null +++ b/modpack.conf @@ -0,0 +1,2 @@ +name=icyessentials +description=IcyEssentials is a collection of essential commands for server administation and moderation.