aboutsummaryrefslogtreecommitdiff
path: root/builtin/game/register.lua
blob: 8908f51f67ffdcb51d2f3b96bb5be34c9a380a1c (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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
-- Minetest: builtin/misc_register.lua

--
-- Make raw registration functions inaccessible to anyone except this file
--

local register_item_raw = core.register_item_raw
core.register_item_raw = nil

local register_alias_raw = core.register_alias_raw
core.register_item_raw = nil

--
-- Item / entity / ABM registration functions
--

core.registered_abms = {}
core.registered_entities = {}
core.registered_items = {}
core.registered_nodes = {}
core.registered_craftitems = {}
core.registered_tools = {}
core.registered_aliases = {}

-- For tables that are indexed by item name:
-- If table[X] does not exist, default to table[core.registered_aliases[X]]
local alias_metatable = {
	__index = function(t, name)
		return rawget(t, core.registered_aliases[name])
	end
}
setmetatable(core.registered_items, alias_metatable)
setmetatable(core.registered_nodes, alias_metatable)
setmetatable(core.registered_craftitems, alias_metatable)
setmetatable(core.registered_tools, alias_metatable)

-- These item names may not be used because they would interfere
-- with legacy itemstrings
local forbidden_item_names = {
	MaterialItem = true,
	MaterialItem2 = true,
	MaterialItem3 = true,
	NodeItem = true,
	node = true,
	CraftItem = true,
	craft = true,
	MBOItem = true,
	ToolItem = true,
	tool = true,
}

local function check_modname_prefix(name)
	if name:sub(1,1) == ":" then
		-- Escape the modname prefix enforcement mechanism
		return name:sub(2)
	else
		-- Modname prefix enforcement
		local expected_prefix = core.get_current_modname() .. ":"
		if name:sub(1, #expected_prefix) ~= expected_prefix then
			error("Name " .. name .. " does not follow naming conventions: " ..
				"\"modname:\" or \":\" prefix required")
		end
		local subname = name:sub(#expected_prefix+1)
		if subname:find("[^abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]") then
			error("Name " .. name .. " does not follow naming conventions: " ..
				"contains unallowed characters")
		end
		return name
	end
end

function core.register_abm(spec)
	-- Add to core.registered_abms
	core.registered_abms[#core.registered_abms+1] = spec
end

function core.register_entity(name, prototype)
	-- Check name
	if name == nil then
		error("Unable to register entity: Name is nil")
	end
	name = check_modname_prefix(tostring(name))

	prototype.name = name
	prototype.__index = prototype  -- so that it can be used as a metatable

	-- Add to core.registered_entities
	core.registered_entities[name] = prototype
end

function core.register_item(name, itemdef)
	-- Check name
	if name == nil then
		error("Unable to register item: Name is nil")
	end
	name = check_modname_prefix(tostring(name))
	if forbidden_item_names[name] then
		error("Unable to register item: Name is forbidden: " .. name)
	end
	itemdef.name = name

	-- Apply defaults and add to registered_* table
	if itemdef.type == "node" then
		-- Use the nodebox as selection box if it's not set manually
		if itemdef.drawtype == "nodebox" and not itemdef.selection_box then
			itemdef.selection_box = itemdef.node_box
		elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then
			itemdef.selection_box = {
				type = "fixed",
				fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
			}
		end
		setmetatable(itemdef, {__index = core.nodedef_default})
		core.registered_nodes[itemdef.name] = itemdef
	elseif itemdef.type == "craft" then
		setmetatable(itemdef, {__index = core.craftitemdef_default})
		core.registered_craftitems[itemdef.name] = itemdef
	elseif itemdef.type == "tool" then
		setmetatable(itemdef, {__index = core.tooldef_default})
		core.registered_tools[itemdef.name] = itemdef
	elseif itemdef.type == "none" then
		setmetatable(itemdef, {__index = core.noneitemdef_default})
	else
		error("Unable to register item: Type is invalid: " .. dump(itemdef))
	end

	-- Flowing liquid uses param2
	if itemdef.type == "node" and itemdef.liquidtype == "flowing" then
		itemdef.paramtype2 = "flowingliquid"
	end

	-- BEGIN Legacy stuff
	if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
		core.register_craft({
			type="cooking",
			output=itemdef.cookresult_itemstring,
			recipe=itemdef.name,
			cooktime=itemdef.furnace_cooktime
		})
	end
	if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then
		core.register_craft({
			type="fuel",
			recipe=itemdef.name,
			burntime=itemdef.furnace_burntime
		})
	end
	-- END Legacy stuff

	-- Disable all further modifications
	getmetatable(itemdef).__newindex = {}

	--core.log("Registering item: " .. itemdef.name)
	core.registered_items[itemdef.name] = itemdef
	core.registered_aliases[itemdef.name] = nil
	register_item_raw(itemdef)
end

function core.register_node(name, nodedef)
	nodedef.type = "node"
	core.register_item(name, nodedef)
end

function core.register_craftitem(name, craftitemdef)
	craftitemdef.type = "craft"

	-- BEGIN Legacy stuff
	if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then
		craftitemdef.inventory_image = craftitemdef.image
	end
	-- END Legacy stuff

	core.register_item(name, craftitemdef)
end

function core.register_tool(name, tooldef)
	tooldef.type = "tool"
	tooldef.stack_max = 1

	-- BEGIN Legacy stuff
	if tooldef.inventory_image == nil and tooldef.image ~= nil then
		tooldef.inventory_image = tooldef.image
	end
	if tooldef.tool_capabilities == nil and
	   (tooldef.full_punch_interval ~= nil or
	    tooldef.basetime ~= nil or
	    tooldef.dt_weight ~= nil or
	    tooldef.dt_crackiness ~= nil or
	    tooldef.dt_crumbliness ~= nil or
	    tooldef.dt_cuttability ~= nil or
	    tooldef.basedurability ~= nil or
	    tooldef.dd_weight ~= nil or
	    tooldef.dd_crackiness ~= nil or
	    tooldef.dd_crumbliness ~= nil or
	    tooldef.dd_cuttability ~= nil) then
		tooldef.tool_capabilities = {
			full_punch_interval = tooldef.full_punch_interval,
			basetime = tooldef.basetime,
			dt_weight = tooldef.dt_weight,
			dt_crackiness = tooldef.dt_crackiness,
			dt_crumbliness = tooldef.dt_crumbliness,
			dt_cuttability = tooldef.dt_cuttability,
			basedurability = tooldef.basedurability,
			dd_weight = tooldef.dd_weight,
			dd_crackiness = tooldef.dd_crackiness,
			dd_crumbliness = tooldef.dd_crumbliness,
			dd_cuttability = tooldef.dd_cuttability,
		}
	end
	-- END Legacy stuff

	core.register_item(name, tooldef)
end

function core.register_alias(name, convert_to)
	if forbidden_item_names[name] then
		error("Unable to register alias: Name is forbidden: " .. name)
	end
	if core.registered_items[name] ~= nil then
		core.log("WARNING: Not registering alias, item with same name" ..
			" is already defined: " .. name .. " -> " .. convert_to)
	else
		--core.log("Registering alias: " .. name .. " -> " .. convert_to)
		core.registered_aliases[name] = convert_to
		register_alias_raw(name, convert_to)
	end
end

local register_biome_raw = core.register_biome
core.registered_biomes = {}
function core.register_biome(biome)
	core.registered_biomes[biome.name] = biome
	register_biome_raw(biome)
end

function core.on_craft(itemstack, player, old_craft_list, craft_inv)
	for _, func in ipairs(core.registered_on_crafts) do
		itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
	end
	return itemstack
end

function core.craft_predict(itemstack, player, old_craft_list, craft_inv)
	for _, func in ipairs(core.registered_craft_predicts) do
		itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
	end
	return itemstack
end

-- Alias the forbidden item names to "" so they can't be
-- created via itemstrings (e.g. /give)
local name
for name in pairs(forbidden_item_names) do
	core.registered_aliases[name] = ""
	register_alias_raw(name, "")
end


-- Deprecated:
-- Aliases for core.register_alias (how ironic...)
--core.alias_node = core.register_alias
--core.alias_tool = core.register_alias
--core.alias_craftitem = core.register_alias

--
-- Built-in node definitions. Also defined in C.
--

core.register_item(":unknown", {
	type = "none",
	description = "Unknown Item",
	inventory_image = "unknown_item.png",
	on_place = core.item_place,
	on_drop = core.item_drop,
	groups = {not_in_creative_inventory=1},
	diggable = true,
})

core.register_node(":air", {
	description = "Air (you hacker you!)",
	inventory_image = "unknown_node.png",
	wield_image = "unknown_node.png",
	drawtype = "airlike",
	paramtype = "light",
	sunlight_propagates = true,
	walkable = false,
	pointable = false,
	diggable = false,
	buildable_to = true,
	air_equivalent = true,
	drop = "",
	groups = {not_in_creative_inventory=1},
})

core.register_node(":ignore", {
	description = "Ignore (you hacker you!)",
	inventory_image = "unknown_node.png",
	wield_image = "unknown_node.png",
	drawtype = "airlike",
	paramtype = "none",
	sunlight_propagates = false,
	walkable = false,
	pointable = false,
	diggable = false,
	buildable_to = true, -- A way to remove accidentally placed ignores
	air_equivalent = true,
	drop = "",
	groups = {not_in_creative_inventory=1},
})

-- The hand (bare definition)
core.register_item(":", {
	type = "none",
	groups = {not_in_creative_inventory=1},
})


function core.override_item(name, redefinition)
	if redefinition.name ~= nil then
		error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2)
	end
	if redefinition.type ~= nil then
		error("Attempt to redefine type of "..name.." to "..dump(redefinition.type), 2)
	end
	local item = core.registered_items[name]
	if not item then
		error("Attempt to override non-existent item "..name, 2)
	end
	for k, v in pairs(redefinition) do
		rawset(item, k, v)
	end
	register_item_raw(item)
end


function core.run_callbacks(callbacks, mode, ...)
	assert(type(callbacks) == "table")
	local cb_len = #callbacks
	if cb_len == 0 then
		if mode == 2 or mode == 3 then
			return true
		elseif mode == 4 or mode == 5 then
			return false
		end
	end
	local ret = nil
	for i = 1, cb_len do
		local cb_ret = callbacks[i](...)

		if mode == 0 and i == 1 then
			ret = cb_ret
		elseif mode == 1 and i == cb_len then
			ret = cb_ret
		elseif mode == 2 then
			if not cb_ret or i == 1 then
				ret = cb_ret
			end
		elseif mode == 3 then
			if cb_ret then
				return cb_ret
			end
			ret = cb_ret
		elseif mode == 4 then
			if (cb_ret and not ret) or i == 1 then
				ret = cb_ret
			end
		elseif mode == 5 and cb_ret then
			return cb_ret
		end
	end
	return ret
end

--
-- Callback registration
--

local function make_registration()
	local t = {}
	local registerfunc = function(func) table.insert(t, func) end
	return t, registerfunc
end

local function make_registration_reverse()
	local t = {}
	local registerfunc = function(func) table.insert(t, 1, func) end
	return t, registerfunc
end

core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
core.registered_globalsteps, core.register_globalstep = make_registration()
core.registered_playerevents, core.register_playerevent = make_registration()
core.registered_on_mapgen_inits, core.register_on_mapgen_init = make_registration()
core.registered_on_shutdown, core.register_on_shutdown = make_registration()
core.registered_on_punchnodes, core.register_on_punchnode = make_registration()
core.registered_on_placenodes, core.register_on_placenode = make_registration()
core.registered_on_dignodes, core.register_on_dignode = make_registration()
core.registered_on_generateds, core.register_on_generated = make_registration()
core.registered_on_newplayers, core.register_on_newplayer = make_registration()
core.registered_on_dieplayers, core.register_on_dieplayer = make_registration()
core.registered_on_respawnplayers, core.register_on_respawnplayer = make_registration()
core.registered_on_prejoinplayers, core.register_on_prejoinplayer = make_registration()
core.registered_on_joinplayers, core.register_on_joinplayer = make_registration()
core.registered_on_leaveplayers, core.register_on_leaveplayer = make_registration()
core.registered_on_player_receive_fields, core.register_on_player_receive_fields = make_registration_reverse()
core.registered_on_cheats, core.register_on_cheat = make_registration()
core.registered_on_crafts, core.register_on_craft = make_registration()
core.registered_craft_predicts, core.register_craft_predict = make_registration()
core.registered_on_protection_violation, core.register_on_protection_violation = make_registration()

{ {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) teleportee = core.get_player_by_name(name) if teleportee and p.x and p.y and p.z then teleportee:setpos(p) return true, "Teleporting to "..core.pos_to_string(p) 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:getpos() end end if teleportee and p then p = find_free_position_near(p) teleportee:setpos(p) return true, "Teleporting to " .. target_name .. " at "..core.pos_to_string(p) end if core.check_player_privs(name, {bring=true}) then 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:setpos(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:getpos() end end if teleportee and p then p = find_free_position_near(p) teleportee:setpos(p) return true, "Teleporting " .. teleportee_name .. " to " .. target_name .. " at " .. core.pos_to_string(p) end 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.setting_set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname, setvalue = string.match(param, "([^ ]+) (.+)") if setname and setvalue then if not core.setting_get(setname) then return false, "Failed. Use '/set -n <name> <value>' to create a new setting." end core.setting_set(setname, setvalue) return true, setname .. " = " .. setvalue end local setname = string.match(param, "([^ ]+)") if setname then local setvalue = core.setting_get(setname) if not setvalue then setvalue = "<not set>" end return true, setname .. " = " .. setvalue end return false, "Invalid parameters (see /help set)." 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() then return false, "Cannot give an unknown item" 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) 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 return true, ("%q %sadded to inventory.") :format(stackstring, partiality) else core.chat_send_player(receiver, ("%q %sadded to inventory.") :format(stackstring, partiality)) return true, ("%q %sadded to %s's inventory.") :format(stackstring, partiality, receiver) end end core.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 return false, "Name and ItemString required" end return handle_give_command("/give", name, toname, itemstring) end, }) core.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 return false, "ItemString required" end return handle_give_command("/giveme", name, name, itemstring) end, }) core.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 return false, "EntityName required" end core.log("action", ("/spawnentity invoked, entityname=%q") :format(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 local p = player:getpos() 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 if player:get_wielded_item():is_empty() then return false, "Unable to pulverize, no item in hand." end 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:get_player_name() if 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 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 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) 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 = "<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 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 = "Print server status", func = function(name, param) return true, core.get_server_status() end, }) core.register_chatcommand("time", { params = "<0...24000>", description = "set time of day", privs = {settime=true}, func = function(name, param) if param == "" then return false, "Missing time." end local newtime = tonumber(param) if newtime == nil then return false, "Invalid time." end core.set_timeofday((newtime % 24000) / 24000) core.log("action", name .. " sets time " .. newtime) return true, "Time of day changed." end, }) core.register_chatcommand("shutdown", { description = "shutdown server", privs = {server=true}, func = function(name, param) core.log("action", name .. " shuts down server") core.request_shutdown() core.chat_send_all("*** Server shutting down (operator request).") end, }) core.register_chatcommand("ban", { params = "<name>", description = "Ban IP of player", privs = {ban=true}, func = function(name, param) if param == "" then return true, "Ban list: " .. core.get_ban_list() 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>", description = "remove IP 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 core.log("action", name .. " kicked " .. tokick) return true, "Kicked " .. tokick end, }) core.register_chatcommand("clearobjects", { description = "clear all objects in world", privs = {server=true}, func = function(name, param) core.log("action", name .. " clears all objects.") core.chat_send_all("Clearing all objects. This may take long." .. " You may experience a timeout. (by " .. name .. ")") core.clear_objects() core.log("action", "Object clearing done.") core.chat_send_all("*** Cleared all objects.") end, }) core.register_chatcommand("msg", { params = "<name> <message>", description = "Send a private message", privs = {shout=true}, func = function(name, param) local sendto, message = param:match("^(%S+)%s(.+)$") if not sendto then return false, "Invalid usage, see /help msg." end if not core.get_player_by_name(sendto) then return false, "The player " .. sendto .. " is not online." end core.log("action", "PM from " .. name .. " to " .. sendto .. ": " .. message) core.chat_send_player(sendto, "PM from " .. name .. ": " .. message) return true, "Message sent." end, })