aboutsummaryrefslogtreecommitdiff
path: root/src/client/inputhandler.cpp
blob: a79b04a9058dcf8910859c3c83d518f458ab5273 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "util/numeric.h"
#include "inputhandler.h"
#include "gui/mainmenumanager.h"
#include "hud.h"

void KeyCache::populate_nonchanging()
{
	key[KeyType::ESC] = EscapeKey;
}

void KeyCache::populate()
{
	key[KeyType::FORWARD] = getKeySetting("keymap_forward");
	key[KeyType::BACKWARD] = getKeySetting("keymap_backward");
	key[KeyType::LEFT] = getKeySetting("keymap_left");
	key[KeyType::RIGHT] = getKeySetting("keymap_right");
	key[KeyType::JUMP] = getKeySetting("keymap_jump");
	key[KeyType::SPECIAL1] = getKeySetting("keymap_special1");
	key[KeyType::SNEAK] = getKeySetting("keymap_sneak");

	key[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward");

	key[KeyType::DROP] = getKeySetting("keymap_drop");
	key[KeyType::INVENTORY] = getKeySetting("keymap_inventory");
	key[KeyType::CHAT] = getKeySetting("keymap_chat");
	key[KeyType::CMD] = getKeySetting("keymap_cmd");
	key[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local");
	key[KeyType::CONSOLE] = getKeySetting("keymap_console");
	key[KeyType::MINIMAP] = getKeySetting("keymap_minimap");
	key[KeyType::FREEMOVE] = getKeySetting("keymap_freemove");
	key[KeyType::PITCHMOVE] = getKeySetting("keymap_pitchmove");
	key[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove");
	key[KeyType::NOCLIP] = getKeySetting("keymap_noclip");
	key[KeyType::HOTBAR_PREV] = getKeySetting("keymap_hotbar_previous");
	key[KeyType::HOTBAR_NEXT] = getKeySetting("keymap_hotbar_next");
	key[KeyType::MUTE] = getKeySetting("keymap_mute");
	key[KeyType::INC_VOLUME] = getKeySetting("keymap_increase_volume");
	key[KeyType::DEC_VOLUME] = getKeySetting("keymap_decrease_volume");
	key[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic");
	key[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot");
	key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud");
	key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat");
	key[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog");
	key[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera");
	key[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug");
	key[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler");
	key[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode");
	key[KeyType::INCREASE_VIEWING_RANGE] =
			getKeySetting("keymap_increase_viewing_range_min");
	key[KeyType::DECREASE_VIEWING_RANGE] =
			getKeySetting("keymap_decrease_viewing_range_min");
	key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect");
	key[KeyType::ZOOM] = getKeySetting("keymap_zoom");

	key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next");
	key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev");
	key[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc");
	key[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec");

	for (int i = 0; i < HUD_HOTBAR_ITEMCOUNT_MAX; i++) {
		std::string slot_key_name = "keymap_slot" + std::to_string(i + 1);
		key[KeyType::SLOT_1 + i] = getKeySetting(slot_key_name.c_str());
	}

	if (handler) {
		// First clear all keys, then re-add the ones we listen for
		handler->dontListenForKeys();
		for (const KeyPress &k : key) {
			handler->listenForKey(k);
		}
		handler->listenForKey(EscapeKey);
		handler->listenForKey(CancelKey);
	}
}

bool MyEventReceiver::OnEvent(const SEvent &event)
{
	/*
		React to nothing here if a menu is active
	*/
	if (isMenuActive()) {
#ifdef HAVE_TOUCHSCREENGUI
		if (m_touchscreengui) {
			m_touchscreengui->Toggle(false);
		}
#endif
		return g_menumgr.preprocessEvent(event);
	}

	// Remember whether each key is down or up
	if (event.EventType == irr::EET_KEY_INPUT_EVENT) {
		const KeyPress &keyCode = event.KeyInput;
		if (keysListenedFor[keyCode]) {
			if (event.KeyInput.PressedDown) {
				keyIsDown.set(keyCode);
				keyWasDown.set(keyCode);
			} else {
				keyIsDown.unset(keyCode);
			}
			return true;
		}
	}

#ifdef HAVE_TOUCHSCREENGUI
	// case of touchscreengui we have to handle different events
	if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) {
		m_touchscreengui->translateEvent(event);
		return true;
	}
#endif

	if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
		/* TODO add a check like:
		if (event.JoystickEvent != joystick_we_listen_for)
			return false;
		*/
		return joystick->handleEvent(event.JoystickEvent);
	}
	// handle mouse events
	if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) {
		if (isMenuActive()) {
			left_active = false;
			middle_active = false;
			right_active = false;
		} else {
			left_active = event.MouseInput.isLeftPressed();
			middle_active = event.MouseInput.isMiddlePressed();
			right_active = event.MouseInput.isRightPressed();

			if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
				leftclicked = true;
			}
			if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) {
				rightclicked = true;
			}
			if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
				leftreleased = true;
			}
			if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) {
				rightreleased = true;
			}
			if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
				mouse_wheel += event.MouseInput.Wheel;
			}
		}
	} else if (event.EventType == irr::EET_LOG_TEXT_EVENT) {
		static const LogLevel irr_loglev_conv[] = {
				LL_VERBOSE, // ELL_DEBUG
				LL_INFO,    // ELL_INFORMATION
				LL_WARNING, // ELL_WARNING
				LL_ERROR,   // ELL_ERROR
				LL_NONE,    // ELL_NONE
		};
		assert(event.LogEvent.Level < ARRLEN(irr_loglev_conv));
		g_logger.log(irr_loglev_conv[event.LogEvent.Level],
				std::string("Irrlicht: ") + event.LogEvent.Text);
		return true;
	}
	/* always return false in order to continue processing events */
	return false;
}

/*
 * RandomInputHandler
 */
s32 RandomInputHandler::Rand(s32 min, s32 max)
{
	return (myrand() % (max - min + 1)) + min;
}

void RandomInputHandler::step(float dtime)
{
	{
		static float counter1 = 0;
		counter1 -= dtime;
		if (counter1 < 0.0) {
			counter1 = 0.1 * Rand(1, 40);
			keydown.toggle(getKeySetting("keymap_jump"));
		}
	}
	{
		static float counter1 = 0;
		counter1 -= dtime;
		if (counter1 < 0.0) {
			counter1 = 0.1 * Rand(1, 40);
			keydown.toggle(getKeySetting("keymap_special1"));
		}
	}
	{
		static float counter1 = 0;
		counter1 -= dtime;
		if (counter1 < 0.0) {
			counter1 = 0.1 * Rand(1, 40);
			keydown.toggle(getKeySetting("keymap_forward"));
		}
	}
	{
		static float counter1 = 0;
		counter1 -= dtime;
		if (counter1 < 0.0) {
			counter1 = 0.1 * Rand(1, 40);
			keydown.toggle(getKeySetting("keymap_left"));
		}
	}
	{
		static float counter1 = 0;
		counter1 -= dtime;
		if (counter1 < 0.0) {
			counter1 = 0.1 * Rand(1, 20);
			mousespeed = v2s32(Rand(-20, 20), Rand(-15, 20));
		}
	}
	{
		static float counter1 = 0;
		counter1 -= dtime;
		if (counter1 < 0.0) {
			counter1 = 0.1 * Rand(1, 30);
			leftdown = !leftdown;
			if (leftdown)
				leftclicked = true;
			if (!leftdown)
				leftreleased = true;
		}
	}
	{
		static float counter1 = 0;
		counter1 -= dtime;
		if (counter1 < 0.0) {
			counter1 = 0.1 * Rand(1, 15);
			rightdown = !rightdown;
			if (rightdown)
				rightclicked = true;
			if (!rightdown)
				rightreleased = true;
		}
	}
	mousepos += mousespeed;
}
za')", privs = {shout=true}, func = function(name, param) core.chat_send_all("* " .. name .. " " .. param) end, }) core.register_chatcommand("admin", { description = "Show the name of the server owner", func = function(name) local admin = core.settings:get("name") if admin then return true, "The administrator of this server is " .. admin .. "." else return false, "There's no administrator named in the config file." end end, }) core.register_chatcommand("privs", { params = "[<name>]", description = "Show privileges of yourself or another player", func = function(caller, param) param = param:trim() local name = (param ~= "" and param or caller) if not core.player_exists(name) then return false, "Player " .. name .. " does not exist." end return true, "Privileges of " .. name .. ": " .. core.privs_to_string( core.get_player_privs(name), ' ') end, }) core.register_chatcommand("haspriv", { params = "<privilege>", description = "Return list of all online players with privilege.", privs = {basic_privs = true}, func = function(caller, param) param = param:trim() if param == "" then return false, "Invalid parameters (see /help haspriv)" end if not core.registered_privileges[param] then return false, "Unknown privilege!" end local privs = core.string_to_privs(param) local players_with_priv = {} for _, player in pairs(core.get_connected_players()) do local player_name = player:get_player_name() if core.check_player_privs(player_name, privs) then table.insert(players_with_priv, player_name) end end return true, "Players online with the \"" .. param .. "\" privilege: " .. table.concat(players_with_priv, ", ") end }) local function handle_grant_command(caller, grantname, grantprivstr) local caller_privs = core.get_player_privs(caller) if not (caller_privs.privs or caller_privs.basic_privs) then return false, "Your privileges are insufficient." end if not core.get_auth_handler().get_auth(grantname) then return false, "Player " .. grantname .. " does not exist." end local grantprivs = core.string_to_privs(grantprivstr) if grantprivstr == "all" then grantprivs = core.registered_privileges end local privs = core.get_player_privs(grantname) local privs_unknown = "" local basic_privs = core.string_to_privs(core.settings:get("basic_privs") or "interact,shout") for priv, _ in pairs(grantprivs) do if not basic_privs[priv] and not caller_privs.privs then return false, "Your privileges are insufficient." end if not core.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 for priv, _ in pairs(grantprivs) do core.run_priv_callbacks(grantname, priv, caller, "grant") end core.set_player_privs(grantname, privs) core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname) if grantname ~= caller then core.chat_send_player(grantname, caller .. " granted you privileges: " .. core.privs_to_string(grantprivs, ' ')) end return true, "Privileges of " .. grantname .. ": " .. core.privs_to_string( core.get_player_privs(grantname), ' ') end core.register_chatcommand("grant", { params = "<name> (<privilege> | all)", description = "Give privileges to player", func = function(name, param) local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") if not grantname or not grantprivstr then return false, "Invalid parameters (see /help grant)" end return handle_grant_command(name, grantname, grantprivstr) end, }) core.register_chatcommand("grantme", { params = "<privilege> | all", description = "Grant privileges to yourself", func = function(name, param) if param == "" then return false, "Invalid parameters (see /help grantme)" end return handle_grant_command(name, name, param) end, }) core.register_chatcommand("revoke", { params = "<name> (<privilege> | all)", description = "Remove privileges from player", privs = {}, func = function(name, param) if not core.check_player_privs(name, {privs=true}) and not core.check_player_privs(name, {basic_privs=true}) then return false, "Your privileges are insufficient." end local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)") if not revoke_name or not revoke_priv_str then return false, "Invalid parameters (see /help revoke)" elseif not core.get_auth_handler().get_auth(revoke_name) then return false, "Player " .. revoke_name .. " does not exist." end local revoke_privs = core.string_to_privs(revoke_priv_str) local privs = core.get_player_privs(revoke_name) local basic_privs = core.string_to_privs(core.settings:get("basic_privs") or "interact,shout") for priv, _ in pairs(revoke_privs) do if not basic_privs[priv] and not core.check_player_privs(name, {privs=true}) then return false, "Your privileges are insufficient." end end if revoke_priv_str == "all" then revoke_privs = privs privs = {} else for priv, _ in pairs(revoke_privs) do privs[priv] = nil end end for priv, _ in pairs(revoke_privs) do core.run_priv_callbacks(revoke_name, priv, name, "revoke") end core.set_player_privs(revoke_name, privs) core.log("action", name..' revoked (' ..core.privs_to_string(revoke_privs, ', ') ..') privileges from '..revoke_name) if revoke_name ~= name then core.chat_send_player(revoke_name, name .. " revoked privileges from you: " .. core.privs_to_string(revoke_privs, ' ')) end return true, "Privileges of " .. revoke_name .. ": " .. core.privs_to_string( core.get_player_privs(revoke_name), ' ') end, }) core.register_chatcommand("setpassword", { params = "<name> <password>", description = "Set player's password", privs = {password=true}, func = function(name, param) local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$") if not toname then toname = param:match("^([^ ]+) *$") raw_password = nil end if not toname then return false, "Name field required" end local act_str_past = "?" local act_str_pres = "?" if not raw_password then core.set_player_password(toname, "") act_str_past = "cleared" act_str_pres = "clears" else core.set_player_password(toname, core.get_password_hash(toname, raw_password)) act_str_past = "set" act_str_pres = "sets" end if toname ~= name then core.chat_send_player(toname, "Your password was " .. act_str_past .. " by " .. name) end core.log("action", name .. " " .. act_str_pres .. " password of " .. toname .. ".") return true, "Password of player \"" .. toname .. "\" " .. act_str_past end, }) core.register_chatcommand("clearpassword", { params = "<name>", description = "Set empty password for a player", privs = {password=true}, func = function(name, param) local toname = param if toname == "" then return false, "Name field required" end core.set_player_password(toname, '') core.log("action", name .. " clears password of " .. toname .. ".") return true, "Password of player \"" .. toname .. "\" cleared" end, }) core.register_chatcommand("auth_reload", { params = "", description = "Reload authentication data", privs = {server=true}, func = function(name, param) local done = core.auth_reload() return done, (done and "Done." or "Failed.") end, }) core.register_chatcommand("remove_player", { params = "<name>", description = "Remove a player's data", privs = {server=true}, func = function(name, param) local toname = param if toname == "" then return false, "Name field required" end local rc = core.remove_player(toname) if rc == 0 then core.log("action", name .. " removed player data of " .. toname .. ".") return true, "Player \"" .. toname .. "\" removed." elseif rc == 1 then return true, "No such player \"" .. toname .. "\" to remove." elseif rc == 2 then return true, "Player \"" .. toname .. "\" is connected, cannot remove." end return false, "Unhandled remove_player return code " .. rc .. "" end, }) core.register_chatcommand("teleport", { params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)", description = "Teleport to position or player", 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 = core.get_node_or_nil(p) if n and n.name then local def = core.registered_nodes[n.name] if def and not def.walkable then return p, true end 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) if p.x and p.y and p.z then local lm = 31000 if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then return false, "Cannot teleport out of map bounds!" end teleportee = core.get_player_by_name(name) if teleportee then teleportee:set_pos(p) return true, "Teleporting to "..core.pos_to_string(p) end end local teleportee = nil local p = nil local target_name = nil target_name = param:match("^([^ ]+)$") teleportee = core.get_player_by_name(name) if target_name then local target = core.get_player_by_name(target_name) if target then p = target:get_pos() end end if teleportee and p then p = find_free_position_near(p) teleportee:set_pos(p) return true, "Teleporting to " .. target_name .. " at "..core.pos_to_string(p) end if not core.check_player_privs(name, {bring=true}) then return false, "You don't have permission to teleport other players (missing bring privilege)" end local teleportee = nil local p = {} local teleportee_name = nil teleportee_name, p.x, p.y, p.z = param:match( "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z) if teleportee_name then teleportee = core.get_player_by_name(teleportee_name) end if teleportee and p.x and p.y and p.z then teleportee:set_pos(p) return true, "Teleporting " .. teleportee_name .. " to " .. core.pos_to_string(p) 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 = core.get_player_by_name(teleportee_name) end if target_name then local target = core.get_player_by_name(target_name) if target then p = target:get_pos() end end if teleportee and p then p = find_free_position_near(p) teleportee:set_pos(p) return true, "Teleporting " .. teleportee_name .. " to " .. target_name .. " at " .. core.pos_to_string(p) end return false, 'Invalid parameters ("' .. param .. '") or player not found (see /help teleport)' end, }) core.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 core.settings:set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname, setvalue = string.match(param, "([^ ]+) (.+)") if setname and setvalue then if not core.settings:get(setname) then return false, "Failed. Use '/set -n <name> <value>' to create a new setting." end core.settings:set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname = string.match(param, "([^ ]+)") if setname then local setvalue = core.settings:get(setname) if not setvalue then setvalue = "<not set>" end return true, setname .. " = " .. setvalue end return false, "Invalid parameters (see /help set)." end, }) local function emergeblocks_callback(pos, action, num_calls_remaining, ctx) if ctx.total_blocks == 0 then ctx.total_blocks = num_calls_remaining + 1 ctx.current_blocks = 0 end ctx.current_blocks = ctx.current_blocks + 1 if ctx.current_blocks == ctx.total_blocks then core.chat_send_player(ctx.requestor_name, string.format("Finished emerging %d blocks in %.2fms.", ctx.total_blocks, (os.clock() - ctx.start_time) * 1000)) end end local function emergeblocks_progress_update(ctx) if ctx.current_blocks ~= ctx.total_blocks then core.chat_send_player(ctx.requestor_name, string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)", ctx.current_blocks, ctx.total_blocks, (ctx.current_blocks / ctx.total_blocks) * 100)) core.after(2, emergeblocks_progress_update, ctx) end end core.register_chatcommand("emergeblocks", { params = "(here [<radius>]) | (<pos1> <pos2>)", description = "Load (or, if nonexistent, generate) map blocks " .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)", privs = {server=true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) if p1 == false then return false, p2 end local context = { current_blocks = 0, total_blocks = 0, start_time = os.clock(), requestor_name = name } core.emerge_area(p1, p2, emergeblocks_callback, context) core.after(2, emergeblocks_progress_update, context) return true, "Started emerge of area ranging from " .. core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) end, }) core.register_chatcommand("deleteblocks", { params = "(here [<radius>]) | (<pos1> <pos2>)", description = "Delete map blocks contained in area pos1 to pos2 " .. "(<pos1> and <pos2> must be in parentheses)", privs = {server=true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) if p1 == false then return false, p2 end if core.delete_area(p1, p2) then return true, "Successfully cleared area ranging from " .. core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) else return false, "Failed to clear one or more blocks in area" end end, }) core.register_chatcommand("fixlight", { params = "(here [<radius>]) | (<pos1> <pos2>)", description = "Resets lighting in the area between pos1 and pos2 " .. "(<pos1> and <pos2> must be in parentheses)", privs = {server = true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) if p1 == false then return false, p2 end if core.fix_light(p1, p2) then return true, "Successfully reset light in the area ranging from " .. core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) else return false, "Failed to load one or more blocks in area" end end, }) core.register_chatcommand("mods", { params = "", description = "List mods installed on the server", privs = {}, func = function(name, param) return true, table.concat(core.get_modnames(), ", ") end, }) local function handle_give_command(cmd, giver, receiver, stackstring) core.log("action", giver .. " invoked " .. cmd .. ', stackstring="' .. stackstring .. '"') local itemstack = ItemStack(stackstring) if itemstack:is_empty() then return false, "Cannot give an empty item" elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then return false, "Cannot give an unknown item" -- Forbid giving 'ignore' due to unwanted side effects elseif itemstack:get_name() == "ignore" then return false, "Giving 'ignore' is not allowed" end local receiverref = core.get_player_by_name(receiver) if receiverref == nil then return false, receiver .. " is not a known player" end local leftover = receiverref:get_inventory():add_item("main", itemstack) local partiality 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 local msg = "%q %sadded to inventory." return true, msg:format(stackstring, partiality) else core.chat_send_player(receiver, ("%q %sadded to inventory.") :format(stackstring, partiality)) local msg = "%q %sadded to %s's inventory." return true, msg:format(stackstring, partiality, receiver) end end core.register_chatcommand("give", { params = "<name> <ItemString> [<count> [<wear>]]", 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 return false, "Name and ItemString required" end return handle_give_command("/give", name, toname, itemstring) end, }) core.register_chatcommand("giveme", { params = "<ItemString> [<count> [<wear>]]", description = "Give item to yourself", privs = {give=true}, func = function(name, param) local itemstring = string.match(param, "(.+)$") if not itemstring then return false, "ItemString required" end return handle_give_command("/giveme", name, name, itemstring) end, }) core.register_chatcommand("spawnentity", { params = "<EntityName> [<X>,<Y>,<Z>]", description = "Spawn entity at given (or your) position", privs = {give=true, interact=true}, func = function(name, param) local entityname, p = string.match(param, "^([^ ]+) *(.*)$") if not entityname then return false, "EntityName required" end core.log("action", ("%s invokes /spawnentity, entityname=%q") :format(name, entityname)) local player = core.get_player_by_name(name) if player == nil then core.log("error", "Unable to spawn entity, player is nil") return false, "Unable to spawn entity, player is nil" end if not core.registered_entities[entityname] then return false, "Cannot spawn an unknown entity" end if p == "" then p = player:get_pos() else p = core.string_to_pos(p) if p == nil then return false, "Invalid parameters ('" .. param .. "')" end end p.y = p.y + 1 core.add_entity(p, entityname) return true, ("%q spawned."):format(entityname) end, }) core.register_chatcommand("pulverize", { params = "", description = "Destroy item in hand", func = function(name, param) local player = core.get_player_by_name(name) if not player then core.log("error", "Unable to pulverize, no player.") return false, "Unable to pulverize, no player." end local wielded_item = player:get_wielded_item() if wielded_item:is_empty() then return false, "Unable to pulverize, no item in hand." end core.log("action", name .. " pulverized \"" .. wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"") player:set_wielded_item(nil) return true, "An item was pulverized." end, }) -- Key = player name core.rollback_punch_callbacks = {} core.register_on_punchnode(function(pos, node, puncher) local name = puncher and puncher:get_player_name() if name and core.rollback_punch_callbacks[name] then core.rollback_punch_callbacks[name](pos, node, puncher) core.rollback_punch_callbacks[name] = nil end end) core.register_chatcommand("rollback_check", { params = "[<range>] [<seconds>] [<limit>]", description = "Check who last touched a node or a node near it" .. " within the time specified by <seconds>. Default: range = 0," .. " seconds = 86400 = 24h, limit = 5", privs = {rollback=true}, func = function(name, param) if not core.settings:get_bool("enable_rollback_recording") then return false, "Rollback functions are disabled." end 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 return false, "That limit is too high!" end core.rollback_punch_callbacks[name] = function(pos, node, puncher) local name = puncher:get_player_name() core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...") local actions = core.rollback_get_node_actions(pos, range, seconds, limit) if not actions then core.chat_send_player(name, "Rollback functions are disabled") return end local num_actions = #actions if num_actions == 0 then core.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] core.chat_send_player(name, ("%s %s %s -> %s %d seconds ago.") :format( core.pos_to_string(action.pos), action.actor, action.oldnode.name, action.newnode.name, time - action.time)) end end return true, "Punch a node (range=" .. range .. ", seconds=" .. seconds .. "s, limit=" .. limit .. ")" end, }) core.register_chatcommand("rollback", { params = "(<name> [<seconds>]) | (:<actor> [<seconds>])", description = "Revert actions of a player. Default for <seconds> is 60", privs = {rollback=true}, func = function(name, param) if not core.settings:get_bool("enable_rollback_recording") then return false, "Rollback functions are disabled." end 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 return false, "Invalid parameters. See /help rollback" .. " and /help rollback_check." end target_name = "player:"..player_name end seconds = tonumber(seconds) or 60 core.chat_send_player(name, "Reverting actions of " .. target_name .. " since " .. seconds .. " seconds.") local success, log = core.rollback_revert_actions_by( target_name, seconds) local response = "" if #log > 100 then response = "(log is too long to show)\n" else for _, line in pairs(log) do response = response .. line .. "\n" end end response = response .. "Reverting actions " .. (success and "succeeded." or "FAILED.") return success, response end, }) core.register_chatcommand("status", { description = "Show server status", func = function(name, param) local status = core.get_server_status(name, false) if status and status ~= "" then return true, status end return false, "This command was disabled by a mod or game" end, }) core.register_chatcommand("time", { params = "[<0..23>:<0..59> | <0..24000>]", description = "Show or set time of day", privs = {}, func = function(name, param) if param == "" then local current_time = math.floor(core.get_timeofday() * 1440) local minutes = current_time % 60 local hour = (current_time - minutes) / 60 return true, ("Current time is %d:%02d"):format(hour, minutes) end local player_privs = core.get_player_privs(name) if not player_privs.settime then return false, "You don't have permission to run this command " .. "(missing privilege: settime)." end local hour, minute = param:match("^(%d+):(%d+)$") if not hour then local new_time = tonumber(param) if not new_time then return false, "Invalid time." end -- Backward compatibility. core.set_timeofday((new_time % 24000) / 24000) core.log("action", name .. " sets time to " .. new_time) return true, "Time of day changed." end hour = tonumber(hour) minute = tonumber(minute) if hour < 0 or hour > 23 then return false, "Invalid hour (must be between 0 and 23 inclusive)." elseif minute < 0 or minute > 59 then return false, "Invalid minute (must be between 0 and 59 inclusive)." end core.set_timeofday((hour * 60 + minute) / 1440) core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute)) return true, "Time of day changed." end, }) core.register_chatcommand("days", { description = "Show day count since world creation", func = function(name, param) return true, "Current day is " .. core.get_day_count() end }) core.register_chatcommand("shutdown", { params = "[<delay_in_seconds> | -1] [reconnect] [<message>]", description = "Shutdown server (-1 cancels a delayed shutdown)", privs = {server=true}, func = function(name, param) local delay, reconnect, message delay, param = param:match("^%s*(%S+)(.*)") if param then reconnect, param = param:match("^%s*(%S+)(.*)") end message = param and param:match("^%s*(.+)") or "" delay = tonumber(delay) or 0 if delay == 0 then core.log("action", name .. " shuts down server") core.chat_send_all("*** Server shutting down (operator request).") end core.request_shutdown(message:trim(), core.is_yes(reconnect), delay) end, }) core.register_chatcommand("ban", { params = "[<name> | <IP_address>]", description = "Ban player or show ban list", privs = {ban=true}, func = function(name, param) if param == "" then local ban_list = core.get_ban_list() if ban_list == "" then return true, "The ban list is empty." else return true, "Ban list: " .. ban_list end end if not core.get_player_by_name(param) then return false, "No such player." end if not core.ban_player(param) then return false, "Failed to ban player." end local desc = core.get_ban_description(param) core.log("action", name .. " bans " .. desc .. ".") return true, "Banned " .. desc .. "." end, }) core.register_chatcommand("unban", { params = "<name> | <IP_address>", description = "Remove player ban", privs = {ban=true}, func = function(name, param) if not core.unban_player_or_ip(param) then return false, "Failed to unban player/IP." end core.log("action", name .. " unbans " .. param) return true, "Unbanned " .. param end, }) core.register_chatcommand("kick", { params = "<name> [<reason>]", description = "Kick a player", privs = {kick=true}, func = function(name, param) local tokick, reason = param:match("([^ ]+) (.+)") tokick = tokick or param if not core.kick_player(tokick, reason) then return false, "Failed to kick player " .. tokick end local log_reason = "" if reason then log_reason = " with reason \"" .. reason .. "\"" end core.log("action", name .. " kicks " .. tokick .. log_reason) return true, "Kicked " .. tokick end, }) core.register_chatcommand("clearobjects", { params = "[full | quick]", description = "Clear all objects in world", privs = {server=true}, func = function(name, param) local options = {} if param == "" or param == "quick" then options.mode = "quick" elseif param == "full" then options.mode = "full" else return false, "Invalid usage, see /help clearobjects." end core.log("action", name .. " clears all objects (" .. options.mode .. " mode).") core.chat_send_all("Clearing all objects. This may take long." .. " You may experience a timeout. (by " .. name .. ")") core.clear_objects(options) core.log("action", "Object clearing done.") core.chat_send_all("*** Cleared all objects.") end, })