minetest-gamehub/gamehub/commands.lua

729 lines
18 KiB
Lua

--[[
gamehub mod (C) shivajiva101@hotmail.com 2019
This file is part of gamehub.
gamehub 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.
gamehub 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 gamehub. If not, see <https://www.gnu.org/licenses/>.
]]
local ie = gamehub.ie
local MP = minetest.get_modpath(minetest.get_current_modname())
local WP = minetest.get_worldpath()
-----------------------
-- Helper Functions --
-----------------------
-- Grant command handler
-- @param caller: player executing the command
-- @param grant_name: player name receiving privs
-- @param grant_priv_str: privileges string
-- @return bool & message
local function handle_grant_command(caller, grant_name, grant_priv_str)
local caller_privs = minetest.get_player_privs(caller)
if not (caller_privs.privs or caller_privs.basic_privs) then
return false, "Your privileges are insufficient."
end
if not minetest.get_auth_handler().get_auth(grant_name) then
return false, "Player " .. grant_name .. " does not exist."
end
local grantprivs = minetest.string_to_privs(grant_priv_str)
local privs = minetest.get_player_privs(grant_name)
local privs_unknown = ""
local basic_privs = minetest.string_to_privs(
minetest.settings:get("basic_privs") or "interact,shout")
if grant_priv_str == "all" then
grantprivs = minetest.registered_privileges
end
for priv, _ in pairs(grantprivs) do
if not basic_privs[priv] and
not minetest.check_player_privs(caller, {privs=true}) then
return true, "Your privileges are insufficient."
end
if not minetest.registered_privileges[priv] then
privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
end
privs[priv] = true
end
if privs_unknown ~= "" then
return false, privs_unknown
end
if not gamehub.player[grant_name] then
gamehub.load_player(grant_name)
if gamehub.player[grant_name] then
gamehub.update_player_field(grant_name, "privs", privs)
gamehub.player[grant_name] = nil
minetest.log("action", caller..
' granted ('..grant_priv_str..
') privileges to '..grant_name)
return true, "Privileges of " .. grant_name .. ": "
.. minetest.privs_to_string(privs, ', ')
else
return false, "No record for "..grant_name
end
end
gamehub.privs[grant_name] = privs
gamehub.update_player_field(grant_name, "privs") -- backup
if gamehub.player[grant_name].game == "world" then
minetest.set_player_privs(grant_name, privs) -- set the privileges
end
minetest.log("action", caller..
' granted ('..minetest.privs_to_string(grantprivs, ', ')..
') privileges to '..grant_name)
if grant_name ~= caller then
minetest.chat_send_player(grant_name, caller
.. " granted you privileges: "
.. minetest.privs_to_string(grantprivs, ' '))
end
return true, "Privileges of " .. grant_name .. ": "
.. minetest.privs_to_string(
minetest.get_player_privs(grant_name), ' ')
end
-- Revoke command handler
-- @param caller: player executing the command
-- @param revoke_name: player name losing privs
-- @param revoke_priv_str: privileges string
-- @return bool & message
local function handle_revoke_command(caller, revoke_name, revoke_priv_str)
local caller_privs = minetest.get_player_privs(caller)
if not (caller_privs.privs or caller_privs.basic_privs) then
return false, "Your privileges are insufficient."
end
if not minetest.get_auth_handler().get_auth(revoke_name) then
return false, "Player " .. revoke_name .. " does not exist."
end
local revoke_privs = minetest.string_to_privs(revoke_priv_str)
local privs = minetest.get_player_privs(revoke_name)
local basic_privs = minetest.string_to_privs(
minetest.settings:get("basic_privs") or "interact,shout")
for priv, _ in pairs(revoke_privs) do
if not basic_privs[priv] and
not minetest.check_player_privs(caller, {privs=true}) then
return true, "Your privileges are insufficient."
end
end
if revoke_priv_str == "all" then
privs = {}
else
for priv, _ in pairs(revoke_privs) do
privs[priv] = nil
end
end
minetest.set_player_privs(revoke_name, privs)
gamehub.privs[revoke_name] = privs
gamehub.update_player_field(revoke_name, "privs")
minetest.log("action", caller..' revoked ('
..minetest.privs_to_string(revoke_privs, ', ')
..') privileges from '..revoke_name)
if revoke_name ~= caller then
minetest.chat_send_player(revoke_name, caller
.. " revoked privileges from you: "
.. minetest.privs_to_string(revoke_privs, ' '))
end
return true, "Privileges of " .. revoke_name .. ": "
.. minetest.privs_to_string(
minetest.get_player_privs(revoke_name), ' ')
end
-- Check path for correct file presence
-- @param path: folder to check
-- @param name: filename without extension
-- @return truth table including count
local check_files = function(path, name)
local extension, file, err
local list = {}
list.n = 0
extension = {"mts", "we", "hub"}
for _, entry in ipairs(extension) do
local filename = path .. name .. "." .. entry
file, err = ie.io.open(filename, "rb")
if err then
list[entry] = false
else
file:close()
list[entry] = true
list.n = list.n + 1
end
end
return list
end
--------------------
-- Admin Commands --
--------------------
minetest.register_chatcommand("hub", {
description = 'gamehub management tool',
params = '{add|del|edit|load|protect|reset|save|stage|unstage} [name|id]',
func = function(name, param)
-- secure access
if not gamehub.privs[name].hub_admin then
return false, "Insufficient privs!"
end
local cmd, helper, list, param2, player
helper = [[Usage:
/hub add
/hub delete <area_id> [true]
/hub edit <game>
/hub load <filename>
/hub protect <area_id> [true]
/hub save <area_id>
/hub stage
/hub unstage <stage_num>
]]
list = {}
player = minetest.get_player_by_name(name)
if not player then
return false, "You need to be playing to use this command!"
end
for word in param:gmatch("%S+") do
list[#list+1] = word
end
if #list < 1 then return false, helper end
cmd = list[1]
if #list == 1 then
if cmd == 'add' then
local area, ctr = gamehub.area_at_pos(player:get_pos())
if not area then
return false, "You must create an area first!"
elseif ctr > 1 then
return false, "Multiple areas detected. Unable to continue!"
else
minetest.show_formspec(name, "hub:add", gamehub.get_add_formspec(area.name))
end
elseif cmd == 'stage' then
local pos = vector.round(player:get_pos())
local facing = {
h = player:get_look_horizontal(),
v = player:get_look_vertical()
}
local area, ctr = gamehub.area_at_pos(pos)
if area and ctr == 1 then
local data = gamehub.game[area.name].data
local stages = data.stages or {}
local new_stage = {
stage = #stages + 1,
pos = {}, -- set on pad placement
dest = pos,
facing = facing
}
stages[#stages+1] = new_stage
data.stages = stages
gamehub.game[area.name].data = data -- update cache
gamehub.update_game_field(area.name, "data")
return true, "Stage " .. #stages + 1 .. " position added to " ..
area.name .. " data"
else
return false, "Multiple areas detected. Unable to continue!"
end
else
-- no matches
return false, helper
end
elseif #list >= 2 then
if cmd == 'delete' then
local id = tonumber(list[2])
if not areas.areas[id] then
return false, "area id " .. id .. " doesn't exist!"
end
local game = areas.areas[id].name
local msg = game .. " game removed!"
if list[3] == "true" then
-- delete area
local area = areas.areas[id]
worldedit.set(area.pos1, area.pos2, "air")
worldedit.clear_objects(area.pos1, area.pos2)
areas:remove(id, true)
areas:save()
msg = msg .. "\nbuild removed!"
end
-- remove from db
gamehub.delete_game(game)
return true, msg
elseif cmd == "edit" then
local game = list[2]
if #list > 2 then
game = table.concat(list, " ", 2)
end
if not gamehub.game[game] then
return false, game .. ' does not exist!'
end
minetest.show_formspec(name, "hub:edit",
gamehub.get_edit_formspec(game))
elseif cmd == 'load' then
-- flatten list values if reqd
local new_name
local old_name = list[2]
if #list > 2 then
old_name = table.concat(list, " ", 2)
end
if gamehub.game[old_name] then
for i=1,100 do
local exists = ([[%s %i]]):format(old_name, i)
if not gamehub.game[exists] then
new_name = exists
break
end
end
end
-- last entry takes precedence
local folders = {
MP .. "/schems/",
WP .. "/schems/"
}
local path, folder, file, err, msg
msg = {}
for i,v in ipairs(folders) do
local check = check_files(v, old_name)
if check.n == 3 then
folder = v
msg[#msg+1] = "file set found in " .. v
elseif check.n > 0 then
for k,val in pairs(check) do
if val then
msg[#msg+1] = v.."."..k.." found..."
else
msg[#msg+1] = v.."."..k.." missing!"
end
end
elseif check.n == 0 then
msg[#msg + 1] = "no files found in " .. v
end
minetest.chat_send_player(name, table.concat(msg, "\n"))
check.n = nil -- reset
end
if not folder then return end
path = folder .. old_name .. ".mts"
-- add mts using player current pos
local pos1 = vector.round(player:get_pos())
err = minetest.place_schematic(pos1, path, nil, nil, true)
if err == nil then
minetest.chat_send_player(name, "could not open file " .. path)
return
end
-- add nodes with metadata
path = folder .. old_name .. ".we"
file, err = ie.io.open(path, "rb")
if err then
minetest.chat_send_player(name, "could not open file "
.. old_name .. ".we")
return
end
local value = file:read("*a")
file:close()
local count = worldedit.deserialize(pos1, value)
minetest.chat_send_player(name, "replaced " .. count ..
" nodes...")
-- load game file
path = folder .. old_name .. ".hub"
file, err = ie.io.open(path, "rb")
if err then
minetest.chat_send_player(name, "could not open file "..
old_name..".hub")
return
end
value = file:read("*a")
file:close()
local game = minetest.deserialize(value)
-- add new area
-- use distance vector to calculate second position
local dist = vector.subtract(game.data.pos2, game.data.pos1)
local pos2 = vector.add(pos1, dist)
local game_name = new_name or game.name
areas:add(name, game_name, pos1, pos2, nil)
areas:save()
-- modify game data
-- stages
local stages = game.data.stages or {}
for i, stage in ipairs(stages) do
-- modify vectors
local pos
dist = vector.subtract(stage.pos, game.data.pos1)
pos = vector.add(pos1, dist)
stage.pos = pos
dist = vector.subtract(stage.dest, game.data.pos1)
pos = vector.add(pos1, dist)
stage.dest = pos
stages[i] = stage
minetest.get_node_timer(stage.pos):start(1.0) -- init
end
game.data.stages = stages
-- reward pad
if game.data.rpad and game.data.rpad.pos then
-- modify vector
dist = vector.subtract(game.data.rpad.pos, game.data.pos1)
game.data.rpad.pos = vector.add(pos1, dist)
minetest.get_node_timer(game.data.rpad.pos):start(1.0) -- init
end
if new_name then
-- change reward pad meta
local meta = minetest.get_meta(game.data.rpad.pos)
meta:set_string("game", game_name)
meta:set_string("infotext", "Step on pad to complete ".. game_name)
end
-- start vector
dist = vector.subtract(game.pos, game.data.pos1)
game.pos = vector.add(pos1, dist)
game.name = game_name
game.data.pos1 = pos1
game.data.pos2 = pos2
game.played = 0
game.completed = 0
-- create new record
gamehub.new_game(game)
elseif cmd == 'protect' then
local id = tonumber(list[2])
if not areas.areas[id] then
return false, "area id " .. id .. " doesn't exist!"
end
-- optional clear param
if #list == 3 then
-- convert to bool
param2 = list[3] == 'true'
end
local count, msg = gamehub.protect(id, param2)
return true, msg .. id .. "\nadded " .. count .. " nodes"
elseif cmd == 'save' then
local id = tonumber(list[2])
if not id or not areas.areas[id] then
return false, "area id " .. id .. " does not exist!"
end
local area = areas.areas[id]
-- serialize metadata
local result, count = gamehub.serialize_meta(area.pos1, area.pos2)
local path = WP .. "/schems"
local filename = path .. "/" .. area.name .. ".we"
local file, err = ie.io.open(filename, "wb")
if err ~= nil then
minetest.log(name, "Could not save file to \"" .. filename .. "\"")
return
end
file:write(result)
file:flush()
file:close()
minetest.chat_send_player(name, "Saved " .. count ..
" nodes to \"" .. filename .. "\"")
-- create schematic
filename = path .. "/" .. area.name .. ".mts"
minetest.create_schematic(area.pos1, area.pos2, nil, filename)
minetest.chat_send_player(name, "Saved \"" .. filename .. "\"")
-- create serialized db entry file
local data = gamehub.game[area.name]
data = minetest.serialize(data)
filename = path .. "/" .. area.name .. ".hub"
file, err = io.open(filename, "wb")
if err ~= nil then
minetest.log(name, "Could not save file to \"" .. filename .. "\"")
return
end
file:write(data)
file:flush()
file:close()
minetest.chat_send_player(name, "Saved \"" .. filename .. "\"")
elseif cmd == 'unstage' then
local num = tonumber(list[2])
local stages, data, fresh, msg
local area, ctr = gamehub.area_at_pos(pos)
if area and ctr == 1 then
data = gamehub.game[area.name].data
stages = data.stages
fresh = {}
msg = "error: stage " .. num .. " doesn't exist"
for i,stage in ipairs(stages) do
-- rebuild table
if stage.stage ~= num then
table.insert(fresh, stage)
else
msg = ("stage %i removed!"):format(i)
end
end
data.stages = fresh
gamehub.game[area.name].data = data
gamehub.update_game_field(area.name, "data")
return true, msg
else
return true, "Multiple areas detected. Unable to continue!"
end
else
return true, helper
end
end
end,
})
------------------------
-- Moderator Commands --
------------------------
-- list player gamehub info
minetest.register_chatcommand("info", {
description = 'List players game (moderator only)',
params = "<name>",
func = function(name, param)
if not gamehub.privs[name].hub_mod then
return false, "Insufficient privs!"
end
-- use invoker for missing param
if param == "" then
param = name
end
local player_data = gamehub.player[param]
if player_data == nil then
-- try to load player from db
player_data = gamehub.load_player(param)
end
if player_data and player_data.game then
return true, param.." is playing "..player_data.game
else
return true, "player isn't registered!"
end
end,
})
-- replace default revoke/grant privileges commands
minetest.override_chatcommand("revoke", {
params = "<name> (<privilege> | all)",
description = "Remove privilege from player",
privs = {privs=true},
func = function(name, param)
local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
if not revoke_name or not revoke_priv_str then
return true, "Invalid parameters (see /help revoke)"
elseif not minetest.get_auth_handler().get_auth(revoke_name) then
return true, "Player " .. revoke_name .. " does not exist."
end
return handle_revoke_command(name, revoke_name, revoke_priv_str)
end,
}
)
minetest.override_chatcommand("grant", {
params = "<name> (<privilege> | all)",
description = "Give privilege to player",
func = function(name, param)
local grant_name, grant_priv_str = string.match(param, "([^ ]+) (.+)")
if not grant_name or not grant_priv_str then
return false, "Invalid parameters (Usage: /hub_grant <player> <privs>)"
end
return handle_grant_command(name, grant_name, grant_priv_str)
end,
}
)
---------------------
-- Player Commands --
---------------------
-- show game menu command
minetest.register_chatcommand("p", {
description = 'Show game menu to player',
params = "<player>",
privs = {shout=true},
func = function(name, param)
if gamehub.jail[name] then
return true, "Access Denied!"
end
local privs = gamehub.privs[name]
local target = name
if privs.hub_mod and param ~= "" and gamehub.player[param] then
target = param -- reassign
end
-- show form
minetest.show_formspec(target, "hub:menu",
gamehub.get_menu_formspec(target))
end,
})
-- exit a subgame
minetest.register_chatcommand("q", {
description = 'quit current game',
params = "<player>",
privs = {shout=true},
func = function(name, param)
if gamehub.jail[name] then
return true, "Access Denied!"
end
local privs = gamehub.privs[name]
local target = name
if privs.hub_mod and param ~= "" then
target = param
end
if gamehub.player[target].game ~= "world" then
gamehub.enter_world(target)
return true, target .. " returned to the world"
end
end,
})
-- show league table
minetest.register_chatcommand("s", {
description = 'view fastest player ranks',
privs = {shout=true},
func = function(name, param)
if gamehub.jail[name] then
return true, "Access Denied!"
end
local privs = gamehub.privs[name]
local target = name
if privs.hub_mod and param ~= "" and gamehub.player[param] then
target = param -- reassign
end
minetest.show_formspec(target, "hub:stats",
gamehub.get_stats_formspec(target))
end
})