summaryrefslogtreecommitdiff
path: root/src/player.cpp
Commit message (Expand)AuthorAge
...
* Switch the license to be LGPLv2/later, with small parts still remaining as GP...Perttu Ahola2012-06-05
* Add 'fly' and 'fast' privileges and the underlying privileges-to-client systemPerttu Ahola2012-03-31
* Simple fix for camera blinking black when jumping into ceiling with current s...Perttu Ahola2012-03-29
* added PlayerSAO and RemotePlayer, removed ServerRemotePlayerKahrl2012-03-29
* Add event manager and use it to trigger soundsPerttu Ahola2012-03-24
* LocalPlayer::getStandingNodePos()Perttu Ahola2012-03-24
* Revert old 4BS/s walk speed for nowPerttu Ahola2012-03-15
* Faster player movement and smaller collision boxPerttu Ahola2012-03-10
* Players stay in environment even when dead, damage flash and fall damage fixesKahrl2012-02-05
* Fix craftresult_is_preview flood in log and remove unnecessary debug outputKahrl2012-01-25
* Inventory menu (with dragging) improved. Crafting is now handled via a IACTIO...Kahrl2012-01-22
* The huge item definition and item namespace unification patch (itemdef), see ...Kahrl2012-01-12
* Move ServerRemotePlayer to a separate filePerttu Ahola2011-12-02
* Make players be again visible from a long distancePerttu Ahola2011-12-02
* Players are more like objects + related stuffPerttu Ahola2011-12-01
* Player-is-SAO WIPPerttu Ahola2011-12-01
* Proper player inventory and hp modification tracking and sending accordinglyPerttu Ahola2011-11-29
* CraftItem rework and Lua interfaceKahrl2011-11-29
* Player movement speed and node access distance checkingPerttu Ahola2011-11-29
* Clean mapnode.h and fix other files accordinglyPerttu Ahola2011-11-29
* GameDef compilesPerttu Ahola2011-11-29
* Create framework for getting rid of global definitions of node/tool/item/what...Perttu Ahola2011-11-29
* Move ContentFeatures to mapnode_contentfeatures.{h,cpp} and clean stuffPerttu Ahola2011-11-29
* Scripting WIP: dynamic object stuffPerttu Ahola2011-11-29
* ServerRemotePlayer implements ServerActiveObjectPerttu Ahola2011-11-29
* Better player damage prevention when falling on unloaded blocks, not involvin...Perttu Ahola2011-10-16
* Fix some "Conditional jump or move depends on uninitialised value(s)" valgrin...Perttu Ahola2011-10-15
* Header file tweaking; mainly for speedPerttu Ahola2011-10-12
* Pause in unloaded territory instead of collideJacobF2011-09-06
* Keep track of player itemGiuseppe Bilotta2011-08-11
* Fixing ladders againMark Holmquist2011-08-01
* Fixed a few problems in the ladder update, and changed the speed to account f...Mark Holmquist2011-08-01
* Ladders implemented!Mark Holmquist2011-08-01
* Fixed handling of inventory in creative mode (normal inventory is not trashed...Perttu Ahola2011-07-30
* extended content-type rangePerttu Ahola2011-07-23
* improved player fall damage detectionPerttu Ahola2011-06-26
* hopefully fixed the privilege problemsPerttu Ahola2011-05-29
* player passwords and privileges in world/auth.txtPerttu Ahola2011-05-29
* Added 'shout' player privilegeCiaran Gultnieks2011-05-23
* All textures are are now searched first from the directory specified by the t...Perttu Ahola2011-05-21
* Passwords - password entry at main menu, stored and checked by serverCiaran Gultnieks2011-05-20
* Fixed two compiler warningsPerttu Ahola2011-05-16
* Privileges to/from string conversion functions standalone, not static membersCiaran Gultnieks2011-05-16
* Allow "all" to be specified in player config file for privilegesCiaran Gultnieks2011-05-16
* Improved server commands and added player permissions.Ciaran Gultnieks2011-05-16
* Code refactoring; split half of main.cpp to game.cpp.Perttu Ahola2011-04-23
* set client to not show hp if server doesn't support itPerttu Ahola2011-04-21
* Some work-in-progress in hp and mobs and a frightening amount of random fixes.Perttu Ahola2011-04-21
* Fixed some problems with crafting and inventoryPerttu Ahola2011-04-11
* mainly work on object scripting apiPerttu Ahola2011-02-23
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
-- Minetest: builtin/chatcommands.lua

--
-- Chat command handler
--

minetest.chatcommands = {}
function minetest.register_chatcommand(cmd, def)
	def = def or {}
	def.params = def.params or ""
	def.description = def.description or ""
	def.privs = def.privs or {}
	minetest.chatcommands[cmd] = def
end

minetest.register_on_chat_message(function(name, message)
	local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
	if not param then
		param = ""
	end
	local cmd_def = minetest.chatcommands[cmd]
	if cmd_def then
		local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs)
		if has_privs then
			cmd_def.func(name, param)
		else
			minetest.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")")
		end
		return true -- handled chat message
	end
	return false
end)

--
-- Chat commands
--
minetest.register_chatcommand("me", {
	params = "<action>",
	description = "chat action (eg. /me orders a pizza)",
	privs = {shout=true},
	func = function(name, param)
		minetest.chat_send_all("* " .. name .. " " .. param)
	end,
})

minetest.register_chatcommand("help", {
	privs = {},
	params = "(nothing)/all/privs/<cmd>",
	description = "Get help for commands or list privileges",
	func = function(name, param)
		local format_help_line = function(cmd, def)
			local msg = "/"..cmd
			if def.params and def.params ~= "" then msg = msg .. " " .. def.params end
			if def.description and def.description ~= "" then msg = msg .. ": " .. def.description end
			return msg
		end
		if param == "" then
			local msg = ""
			cmds = {}
			for cmd, def in pairs(minetest.chatcommands) do
				if minetest.check_player_privs(name, def.privs) then
					table.insert(cmds, cmd)
				end
			end
			minetest.chat_send_player(name, "Available commands: "..table.concat(cmds, " "))
			minetest.chat_send_player(name, "Use '/help <cmd>' to get more information, or '/help all' to list everything.")
		elseif param == "all" then
			minetest.chat_send_player(name, "Available commands:")
			for cmd, def in pairs(minetest.chatcommands) do
				if minetest.check_player_privs(name, def.privs) then
					minetest.chat_send_player(name, format_help_line(cmd, def))
				end
			end
		elseif param == "privs" then
			minetest.chat_send_player(name, "Available privileges:")
			for priv, def in pairs(minetest.registered_privileges) do
				minetest.chat_send_player(name, priv..": "..def.description)
			end
		else
			local cmd = param
			def = minetest.chatcommands[cmd]
			if not def then
				minetest.chat_send_player(name, "Command not available: "..cmd)
			else
				minetest.chat_send_player(name, format_help_line(cmd, def))
			end
		end
	end,
})
minetest.register_chatcommand("privs", {
	params = "<name>",
	description = "print out privileges of player",
	func = function(name, param)
		if param == "" then
			param = name
		else
			--[[if not minetest.check_player_privs(name, {privs=true}) then
				minetest.chat_send_player(name, "Privileges of "..param.." are hidden from you.")
				return
			end]]
		end
		minetest.chat_send_player(name, "Privileges of "..param..": "..minetest.privs_to_string(minetest.get_player_privs(param), ' '))
	end,
})
minetest.register_chatcommand("grant", {
	params = "<name> <privilege>|all",
	description = "Give privilege to player",
	privs = {},
	func = function(name, param)
		if not minetest.check_player_privs(name, {privs=true}) and 
				not minetest.check_player_privs(name, {basic_privs=true}) then
			minetest.chat_send_player(name, "Your privileges are insufficient.")
			return
		end
		local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
		if not grantname or not grantprivstr then
			minetest.chat_send_player(name, "Invalid parameters (see /help grant)")
			return
		elseif not minetest.auth_table[grantname] then
			minetest.chat_send_player(name, "Player "..grantname.." does not exist.")
			return
		end
		local grantprivs = minetest.string_to_privs(grantprivstr)
		if grantprivstr == "all" then
			grantprivs = minetest.registered_privileges
		end
		local privs = minetest.get_player_privs(grantname)
		local privs_known = true
		for priv, _ in pairs(grantprivs) do
			if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not minetest.check_player_privs(name, {privs=true}) then
				minetest.chat_send_player(name, "Your privileges are insufficient.")
				return
			end
			if not minetest.registered_privileges[priv] then
				minetest.chat_send_player(name, "Unknown privilege: "..priv)
				privs_known = false
			end
			privs[priv] = true
		end
		if not privs_known then
			return
		end
		minetest.set_player_privs(grantname, privs)
		minetest.log(name..' granted ('..minetest.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
		minetest.chat_send_player(name, "Privileges of "..grantname..": "..minetest.privs_to_string(minetest.get_player_privs(grantname), ' '))
		if grantname ~= name then
			minetest.chat_send_player(grantname, name.." granted you privileges: "..minetest.privs_to_string(grantprivs, ' '))
		end
	end,
})
minetest.register_chatcommand("revoke", {
	params = "<name> <privilege>|all",
	description = "Remove privilege from player",
	privs = {},
	func = function(name, param)
		if not minetest.check_player_privs(name, {privs=true}) and 
				not minetest.check_player_privs(name, {basic_privs=true}) then
			minetest.chat_send_player(name, "Your privileges are insufficient.")
			return
		end
		local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
		if not revokename or not revokeprivstr then
			minetest.chat_send_player(name, "Invalid parameters (see /help revoke)")
			return
		elseif not minetest.auth_table[revokename] then
			minetest.chat_send_player(name, "Player "..revokename.." does not exist.")
			return
		end
		local revokeprivs = minetest.string_to_privs(revokeprivstr)
		local privs = minetest.get_player_privs(revokename)
		for priv, _ in pairs(revokeprivs) do
			if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not minetest.check_player_privs(name, {privs=true}) then
				minetest.chat_send_player(name, "Your privileges are insufficient.")
				return
			end
		end
		if revokeprivstr == "all" then
			privs = {}
		else
			for priv, _ in pairs(revokeprivs) do
				privs[priv] = nil
			end
		end
		minetest.set_player_privs(revokename, privs)
		minetest.log(name..' revoked ('..minetest.privs_to_string(revokeprivs, ', ')..') privileges from '..revokename)
		minetest.chat_send_player(name, "Privileges of "..revokename..": "..minetest.privs_to_string(minetest.get_player_privs(revokename), ' '))
		if revokename ~= name then
			minetest.chat_send_player(revokename, name.." revoked privileges from you: "..minetest.privs_to_string(revokeprivs, ' '))
		end
	end,
})
minetest.register_chatcommand("setpassword", {
	params = "<name> <password>",
	description = "set given password",
	privs = {password=true},
	func = function(name, param)
		local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
		if not toname then
			toname = string.match(param, "^([^ ]+) *$")
			raw_password = nil
		end
		if not toname then
			minetest.chat_send_player(name, "Name field required")
			return
		end
		local actstr = "?"
		if not raw_password then
			minetest.set_player_password(toname, "")
			actstr = "cleared"
		else
			minetest.set_player_password(toname, minetest.get_password_hash(toname, raw_password))
			actstr = "set"
		end
		minetest.chat_send_player(name, "Password of player \""..toname.."\" "..actstr)
		if toname ~= name then
			minetest.chat_send_player(toname, "Your password was "..actstr.." by "..name)
		end
	end,
})
minetest.register_chatcommand("clearpassword", {
	params = "<name>",
	description = "set empty password",
	privs = {password=true},
	func = function(name, param)
		toname = param
		if toname == "" then
			minetest.chat_send_player(name, "Name field required")
			return
		end
		minetest.set_player_password(toname, '')
		minetest.chat_send_player(name, "Password of player \""..toname.."\" cleared")
	end,
})

minetest.register_chatcommand("auth_reload", {
	params = "",
	description = "reload authentication data",
	privs = {server=true},
	func = function(name, param)
		local done = minetest.auth_reload()
		if done then
			minetest.chat_send_player(name, "Done.")
		else
			minetest.chat_send_player(name, "Failed.")
		end
	end,
})

minetest.register_chatcommand("teleport", {
	params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
	description = "teleport to given position",
	privs = {teleport=true},
	func = function(name, param)
		-- Returns (pos, true) if found, otherwise (pos, false)
		local function find_free_position_near(pos)
			local tries = {
				{x=1,y=0,z=0},
				{x=-1,y=0,z=0},
				{x=0,y=0,z=1},
				{x=0,y=0,z=-1},
			}
			for _, d in ipairs(tries) do
				local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
				local n = minetest.get_node(p)
				if not minetest.registered_nodes[n.name].walkable then
					return p, true
				end
			end
			return pos, false
		end

		local teleportee = nil
		local p = {}
		p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
		p.x = tonumber(p.x)
		p.y = tonumber(p.y)
		p.z = tonumber(p.z)
		teleportee = minetest.get_player_by_name(name)
		if teleportee and p.x and p.y and p.z then
			minetest.chat_send_player(name, "Teleporting to ("..p.x..", "..p.y..", "..p.z..")")
			teleportee:setpos(p)
			return
		end
		
		local teleportee = nil
		local p = nil
		local target_name = nil
		target_name = string.match(param, "^([^ ]+)$")
		teleportee = minetest.get_player_by_name(name)
		if target_name then
			local target = minetest.get_player_by_name(target_name)
			if target then
				p = target:getpos()
			end
		end
		if teleportee and p then
			p = find_free_position_near(p)
			minetest.chat_send_player(name, "Teleporting to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")")
			teleportee:setpos(p)
			return
		end
		
		if minetest.check_player_privs(name, {bring=true}) then
			local teleportee = nil
			local p = {}
			local teleportee_name = nil
			teleportee_name, p.x, p.y, p.z = string.match(param, "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
			p.x = tonumber(p.x)
			p.y = tonumber(p.y)
			p.z = tonumber(p.z)
			if teleportee_name then
				teleportee = minetest.get_player_by_name(teleportee_name)
			end
			if teleportee and p.x and p.y and p.z then
				minetest.chat_send_player(name, "Teleporting "..teleportee_name.." to ("..p.x..", "..p.y..", "..p.z..")")
				teleportee:setpos(p)
				return
			end
			
			local teleportee = nil
			local p = nil
			local teleportee_name = nil
			local target_name = nil
			teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
			if teleportee_name then
				teleportee = minetest.get_player_by_name(teleportee_name)
			end
			if target_name then
				local target = minetest.get_player_by_name(target_name)
				if target then
					p = target:getpos()
				end
			end
			if teleportee and p then
				p = find_free_position_near(p)
				minetest.chat_send_player(name, "Teleporting "..teleportee_name.." to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")")
				teleportee:setpos(p)
				return
			end
		end

		minetest.chat_send_player(name, "Invalid parameters (\""..param.."\") or player not found (see /help teleport)")
		return
	end,
})

minetest.register_chatcommand("set", {
	params = "[-n] <name> <value> | <name>",
	description = "set or read server configuration setting",
	privs = {server=true},
	func = function(name, param)
		local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
		if arg and arg == "-n" and setname and setvalue then
			minetest.setting_set(setname, setvalue)
			minetest.chat_send_player(name, setname.." = "..setvalue)
			return
		end
		local setname, setvalue = string.match(param, "([^ ]+) (.+)")
		if setname and setvalue then
			if not minetest.setting_get(setname) then
				minetest.chat_send_player(name, "Failed. Use '/set -n <name> <value>' to create a new setting.")
				return
			end
			minetest.setting_set(setname, setvalue)
			minetest.chat_send_player(name, setname.." = "..setvalue)
			return
		end
		local setname = string.match(param, "([^ ]+)")
		if setname then
			local setvalue = minetest.setting_get(setname)
			if not setvalue then
				setvalue = "<not set>"
			end
			minetest.chat_send_player(name, setname.." = "..setvalue)
			return
		end
		minetest.chat_send_player(name, "Invalid parameters (see /help set)")
	end,
})

minetest.register_chatcommand("mods", {
	params = "",
	description = "lists mods installed on the server",
	privs = {},
	func = function(name, param)
		local response = ""
		local modnames = minetest.get_modnames()
		for i, mod in ipairs(modnames) do
			response = response .. mod
			-- Add space if not at the end
			if i ~= #modnames then
				response = response .. " "
			end
		end
		minetest.chat_send_player(name, response)
	end,
})

local function handle_give_command(cmd, giver, receiver, stackstring)
	minetest.log("action", giver.." invoked "..cmd..', stackstring="'
			..stackstring..'"')
	minetest.log(cmd..' invoked, stackstring="'..stackstring..'"')
	local itemstack = ItemStack(stackstring)
	if itemstack:is_empty() then
		minetest.chat_send_player(giver, 'error: cannot give an empty item')
		return
	elseif not itemstack:is_known() then
		minetest.chat_send_player(giver, 'error: cannot give an unknown item')
		return
	end
	local receiverref = minetest.get_player_by_name(receiver)
	if receiverref == nil then
		minetest.chat_send_player(giver, receiver..' is not a known player')
		return
	end
	local leftover = receiverref:get_inventory():add_item("main", itemstack)
	if leftover:is_empty() then
		partiality = ""
	elseif leftover:get_count() == itemstack:get_count() then
		partiality = "could not be "
	else
		partiality = "partially "
	end
	-- The actual item stack string may be different from what the "giver"
	-- entered (e.g. big numbers are always interpreted as 2^16-1).
	stackstring = itemstack:to_string()
	if giver == receiver then
		minetest.chat_send_player(giver, '"'..stackstring
			..'" '..partiality..'added to inventory.');
	else
		minetest.chat_send_player(giver, '"'..stackstring
			..'" '..partiality..'added to '..receiver..'\'s inventory.');
		minetest.chat_send_player(receiver, '"'..stackstring
			..'" '..partiality..'added to inventory.');
	end
end

minetest.register_chatcommand("give", {
	params = "<name> <itemstring>",
	description = "give item to player",
	privs = {give=true},
	func = function(name, param)
		local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
		if not toname or not itemstring then
			minetest.chat_send_player(name, "name and itemstring required")
			return
		end
		handle_give_command("/give", name, toname, itemstring)
	end,
})
minetest.register_chatcommand("giveme", {
	params = "<itemstring>",
	description = "give item to yourself",
	privs = {give=true},
	func = function(name, param)
		local itemstring = string.match(param, "(.+)$")
		if not itemstring then
			minetest.chat_send_player(name, "itemstring required")
			return
		end
		handle_give_command("/giveme", name, name, itemstring)
	end,
})
minetest.register_chatcommand("spawnentity", {
	params = "<entityname>",
	description = "spawn entity at your position",
	privs = {give=true, interact=true},
	func = function(name, param)
		local entityname = string.match(param, "(.+)$")
		if not entityname then
			minetest.chat_send_player(name, "entityname required")
			return
		end
		minetest.log("action", '/spawnentity invoked, entityname="'..entityname..'"')
		local player = minetest.get_player_by_name(name)
		if player == nil then
			minetest.log("error", "Unable to spawn entity, player is nil")
			return true -- Handled chat message
		end
		local p = player:getpos()
		p.y = p.y + 1
		minetest.add_entity(p, entityname)
		minetest.chat_send_player(name, '"'..entityname
				..'" spawned.');
	end,
})
minetest.register_chatcommand("pulverize", {
	params = "",
	description = "delete item in hand",
	privs = {},
	func = function(name, param)
		local player = minetest.get_player_by_name(name)
		if player == nil then
			minetest.log("error", "Unable to pulverize, player is nil")
			return true -- Handled chat message
		end
		if player:get_wielded_item():is_empty() then
			minetest.chat_send_player(name, 'Unable to pulverize, no item in hand.')
		else
			player:set_wielded_item(nil)
			minetest.chat_send_player(name, 'An item was pulverized.')
		end
	end,
})

-- Key = player name
minetest.rollback_punch_callbacks = {}

minetest.register_on_punchnode(function(pos, node, puncher)
	local name = puncher:get_player_name()
	if minetest.rollback_punch_callbacks[name] then
		minetest.rollback_punch_callbacks[name](pos, node, puncher)
		minetest.rollback_punch_callbacks[name] = nil
	end
end)

minetest.register_chatcommand("rollback_check", {
	params = "[<range>] [<seconds>] [limit]",
	description = "check who has last touched a node or near it, "..
			"max. <seconds> ago (default range=0, seconds=86400=24h, limit=5)",
	privs = {rollback=true},
	func = function(name, param)
		local range, seconds, limit =
			param:match("(%d+) *(%d*) *(%d*)")
		range = tonumber(range) or 0
		seconds = tonumber(seconds) or 86400
		limit = tonumber(limit) or 5
		if limit > 100 then
			minetest.chat_send_player(name, "That limit is too high!")
			return
		end
		minetest.chat_send_player(name, "Punch a node (range="..
				range..", seconds="..seconds.."s, limit="..limit..")")

		minetest.rollback_punch_callbacks[name] = function(pos, node, puncher)
			local name = puncher:get_player_name()
			minetest.chat_send_player(name, "Checking "..minetest.pos_to_string(pos).."...")
			local actions = minetest.rollback_get_node_actions(pos, range, seconds, limit)
			local num_actions = #actions
			if num_actions == 0 then
				minetest.chat_send_player(name, "Nobody has touched the "..
						"specified location in "..seconds.." seconds")
				return
			end
			local time = os.time()
			for i = num_actions, 1, -1 do
				local action = actions[i]
				minetest.chat_send_player(name,
					("%s %s %s -> %s %d seconds ago.")
						:format(
							minetest.pos_to_string(action.pos),
							action.actor,
							action.oldnode.name,
							action.newnode.name,
							time - action.time))
			end
		end
	end,
})

minetest.register_chatcommand("rollback", {
	params = "<player name> [<seconds>] | :<actor> [<seconds>]",
	description = "revert actions of a player; default for <seconds> is 60",
	privs = {rollback=true},
	func = function(name, param)
		local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
		if not target_name then
			local player_name = nil
			player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
			if not player_name then
				minetest.chat_send_player(name, "Invalid parameters. See /help rollback and /help rollback_check")
				return
			end
			target_name = "player:"..player_name
		end
		seconds = tonumber(seconds) or 60
		minetest.chat_send_player(name, "Reverting actions of "..
				target_name.." since "..seconds.." seconds.")
		local success, log = minetest.rollback_revert_actions_by(
				target_name, seconds)
		if #log > 100 then
			minetest.chat_send_player(name, "(log is too long to show)")
		else
			for _, line in pairs(log) do
				minetest.chat_send_player(name, line)
			end
		end
		if success then
			minetest.chat_send_player(name, "Reverting actions succeeded.")
		else
			minetest.chat_send_player(name, "Reverting actions FAILED.")
		end
	end,
})

minetest.register_chatcommand("status", {
	params = "",
	description = "print server status line",
	privs = {},
	func = function(name, param)
		minetest.chat_send_player(name, minetest.get_server_status())
	end,
})

minetest.register_chatcommand("time", {
	params = "<0...24000>",
	description = "set time of day",
	privs = {settime=true},
	func = function(name, param)
		if param == "" then
			minetest.chat_send_player(name, "Missing parameter")
			return
		end
		local newtime = tonumber(param)
		if newtime == nil then
			minetest.chat_send_player(name, "Invalid time")
		else
			minetest.set_timeofday((newtime % 24000) / 24000)
			minetest.chat_send_player(name, "Time of day changed.")
			minetest.log("action", name .. " sets time " .. newtime)
		end
	end,
})

minetest.register_chatcommand("shutdown", {
	params = "",
	description = "shutdown server",
	privs = {server=true},
	func = function(name, param)
		minetest.log("action", name .. " shuts down server")
		minetest.request_shutdown()
		minetest.chat_send_all("*** Server shutting down (operator request).")
	end,
})

minetest.register_chatcommand("ban", {
	params = "<name>",
	description = "ban IP of player",
	privs = {ban=true},
	func = function(name, param)
		if param == "" then
			minetest.chat_send_player(name, "Ban list: " .. minetest.get_ban_list())
			return
		end
		if not minetest.get_player_by_name(param) then
			minetest.chat_send_player(name, "No such player")
			return
		end
		if not minetest.ban_player(param) then
			minetest.chat_send_player(name, "Failed to ban player")
		else
			local desc = minetest.get_ban_description(param)
			minetest.chat_send_player(name, "Banned " .. desc .. ".")
			minetest.log("action", name .. " bans " .. desc .. ".")
		end
	end,
})

minetest.register_chatcommand("unban", {
	params = "<name/ip>",
	description = "remove IP ban",
	privs = {ban=true},
	func = function(name, param)
		if not minetest.unban_player_or_ip(param) then
			minetest.chat_send_player(name, "Failed to unban player/IP")
		else
			minetest.chat_send_player(name, "Unbanned " .. param)
			minetest.log("action", name .. " unbans " .. param)
		end
	end,
})

minetest.register_chatcommand("clearobjects", {
	params = "",
	description = "clear all objects in world",
	privs = {server=true},
	func = function(name, param)
		minetest.log("action", name .. " clears all objects")
		minetest.chat_send_all("Clearing all objects.  This may take long.  You may experience a timeout.  (by " .. name .. ")")
		minetest.clear_objects()
		minetest.log("action", "object clearing done")
		minetest.chat_send_all("*** Cleared all objects.")
	end,
})

minetest.register_chatcommand("msg", {
	params = "<name> <message>",
	description = "Send a private message",
	privs = {shout=true},
	func = function(name, param)
		local found, _, sendto, message = param:find("^([^%s]+)%s(.+)$")
		if found then
			if minetest.get_player_by_name(sendto) then
				minetest.log("action", "PM from "..name.." to "..sendto..": "..message)
				minetest.chat_send_player(sendto, "PM from "..name..": "..message, false)
				minetest.chat_send_player(name, "Message sent")
			else
				minetest.chat_send_player(name, "The player "..sendto.." is not online")
			end
		else
			minetest.chat_send_player(name, "Invalid usage, see /help msg")
		end
	end,
})