aboutsummaryrefslogtreecommitdiff
path: root/builtin/game
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/game')
-rw-r--r--builtin/game/auth.lua7
-rw-r--r--builtin/game/chat.lua853
-rw-r--r--builtin/game/falling.lua93
-rw-r--r--builtin/game/features.lua3
-rw-r--r--builtin/game/forceloading.lua9
-rw-r--r--builtin/game/init.lua2
-rw-r--r--builtin/game/item.lua164
-rw-r--r--builtin/game/misc.lua86
-rw-r--r--builtin/game/privileges.lua47
-rw-r--r--builtin/game/register.lua12
-rw-r--r--builtin/game/voxelarea.lua14
11 files changed, 763 insertions, 527 deletions
diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua
index fc061666c..e7d502bb3 100644
--- a/builtin/game/auth.lua
+++ b/builtin/game/auth.lua
@@ -87,6 +87,10 @@ core.builtin_auth_handler = {
core.settings:get("default_password")))
end
+ auth_entry.privileges = privileges
+
+ core_auth.save(auth_entry)
+
-- Run grant callbacks
for priv, _ in pairs(privileges) do
if not auth_entry.privileges[priv] then
@@ -100,9 +104,6 @@ core.builtin_auth_handler = {
core.run_priv_callbacks(name, priv, nil, "revoke")
end
end
-
- auth_entry.privileges = privileges
- core_auth.save(auth_entry)
core.notify_authentication_modified(name)
end,
reload = function()
diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua
index 945707623..b73e32876 100644
--- a/builtin/game/chat.lua
+++ b/builtin/game/chat.lua
@@ -1,5 +1,7 @@
-- Minetest: builtin/game/chat.lua
+local S = core.get_translator("__builtin")
+
-- Helper function that implements search and replace without pattern matching
-- Returns the string and a boolean indicating whether or not the string was modified
local function safe_gsub(s, replace, with)
@@ -45,6 +47,8 @@ end
core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
+local msg_time_threshold =
+ tonumber(core.settings:get("chatcommand_msg_time_threshold")) or 0.1
core.register_on_chat_message(function(name, message)
if message:sub(1,1) ~= "/" then
return
@@ -52,7 +56,7 @@ core.register_on_chat_message(function(name, message)
local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
if not cmd then
- core.chat_send_player(name, "-!- Empty command")
+ core.chat_send_player(name, "-!- "..S("Empty command."))
return true
end
@@ -65,15 +69,17 @@ core.register_on_chat_message(function(name, message)
local cmd_def = core.registered_chatcommands[cmd]
if not cmd_def then
- core.chat_send_player(name, "-!- Invalid command: " .. cmd)
+ core.chat_send_player(name, "-!- "..S("Invalid command: @1", cmd))
return true
end
local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
if has_privs then
core.set_last_run_mod(cmd_def.mod_origin)
+ local t_before = core.get_us_time()
local success, result = cmd_def.func(name, param)
+ local delay = (core.get_us_time() - t_before) / 1000000
if success == false and result == nil then
- core.chat_send_player(name, "-!- Invalid command usage")
+ core.chat_send_player(name, "-!- "..S("Invalid command usage."))
local help_def = core.registered_chatcommands["help"]
if help_def then
local _, helpmsg = help_def.func(name, cmd)
@@ -81,13 +87,27 @@ core.register_on_chat_message(function(name, message)
core.chat_send_player(name, helpmsg)
end
end
- elseif result then
- core.chat_send_player(name, result)
+ else
+ if delay > msg_time_threshold then
+ -- Show how much time it took to execute the command
+ if result then
+ result = result .. core.colorize("#f3d2ff", S(" (@1 s)",
+ string.format("%.5f", delay)))
+ else
+ result = core.colorize("#f3d2ff", S(
+ "Command execution took @1 s",
+ string.format("%.5f", delay)))
+ end
+ end
+ if result then
+ core.chat_send_player(name, result)
+ end
end
else
- core.chat_send_player(name, "You don't have permission"
- .. " to run this command (missing privileges: "
- .. table.concat(missing_privs, ", ") .. ")")
+ core.chat_send_player(name,
+ S("You don't have permission to run this command "
+ .. "(missing privileges: @1).",
+ table.concat(missing_privs, ", ")))
end
return true -- Handled chat message
end)
@@ -107,12 +127,13 @@ local function parse_range_str(player_name, str)
if args[1] == "here" then
p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
if p1 == nil then
- return false, "Unable to get player " .. player_name .. " position"
+ return false, S("Unable to get position of player @1.", player_name)
end
else
p1, p2 = core.string_to_area(str)
if p1 == nil then
- return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
+ return false, S("Incorrect area format. "
+ .. "Expected: (x1,y1,z1) (x2,y2,z2)")
end
end
@@ -123,9 +144,9 @@ end
-- Chat commands
--
core.register_chatcommand("me", {
- params = "<action>",
- description = "Show chat action (e.g., '/me orders a pizza' displays"
- .. " '<player name> orders a pizza')",
+ params = S("<action>"),
+ description = S("Show chat action (e.g., '/me orders a pizza' "
+ .. "displays '<player name> orders a pizza')"),
privs = {shout=true},
func = function(name, param)
core.chat_send_all("* " .. name .. " " .. param)
@@ -134,43 +155,54 @@ core.register_chatcommand("me", {
})
core.register_chatcommand("admin", {
- description = "Show the name of the server owner",
+ description = S("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 .. "."
+ return true, S("The administrator of this server is @1.", admin)
else
- return false, "There's no administrator named in the config file."
+ return false, S("There's no administrator named "
+ .. "in the config file.")
end
end,
})
+local function privileges_of(name, privs)
+ if not privs then
+ privs = core.get_player_privs(name)
+ end
+ local privstr = core.privs_to_string(privs, ", ")
+ if privstr == "" then
+ return S("@1 does not have any privileges.", name)
+ else
+ return S("Privileges of @1: @2", name, privstr)
+ end
+end
+
core.register_chatcommand("privs", {
- params = "[<name>]",
- description = "Show privileges of yourself or another player",
+ params = S("[<name>]"),
+ description = S("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."
+ return false, S("Player @1 does not exist.", name)
end
- return true, "Privileges of " .. name .. ": "
- .. core.privs_to_string(
- core.get_player_privs(name), ", ")
+ return true, privileges_of(name)
end,
})
core.register_chatcommand("haspriv", {
- params = "<privilege>",
- description = "Return list of all online players with privilege.",
+ params = S("<privilege>"),
+ description = S("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)"
+ return false, S("Invalid parameters (see /help haspriv).")
end
if not core.registered_privileges[param] then
- return false, "Unknown privilege!"
+ return false, S("Unknown privilege!")
end
local privs = core.string_to_privs(param)
local players_with_priv = {}
@@ -180,19 +212,25 @@ core.register_chatcommand("haspriv", {
table.insert(players_with_priv, player_name)
end
end
- return true, "Players online with the \"" .. param .. "\" privilege: " ..
- table.concat(players_with_priv, ", ")
+ if #players_with_priv == 0 then
+ return true, S("No online player has the \"@1\" privilege.",
+ param)
+ else
+ return true, S("Players online with the \"@1\" privilege: @2",
+ param,
+ table.concat(players_with_priv, ", "))
+ end
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."
+ return false, S("Your privileges are insufficient.")
end
if not core.get_auth_handler().get_auth(grantname) then
- return false, "Player " .. grantname .. " does not exist."
+ return false, S("Player @1 does not exist.", grantname)
end
local grantprivs = core.string_to_privs(grantprivstr)
if grantprivstr == "all" then
@@ -204,50 +242,51 @@ local function handle_grant_command(caller, grantname, grantprivstr)
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."
+ return false, S("Your privileges are insufficient. "..
+ "'@1' only allows you to grant: @2",
+ "basic_privs",
+ core.privs_to_string(basic_privs, ', '))
end
if not core.registered_privileges[priv] then
- privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
+ privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
end
privs[priv] = true
end
if privs_unknown ~= "" then
return false, privs_unknown
end
+ core.set_player_privs(grantname, privs)
for priv, _ in pairs(grantprivs) do
-- call the on_grant callbacks
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, ' '))
+ core.chat_send_player(grantname,
+ S("@1 granted you privileges: @2", caller,
+ core.privs_to_string(grantprivs, ', ')))
end
- return true, "Privileges of " .. grantname .. ": "
- .. core.privs_to_string(
- core.get_player_privs(grantname), ' ')
+ return true, privileges_of(grantname)
end
core.register_chatcommand("grant", {
- params = "<name> (<privilege> | all)",
- description = "Give privileges to player",
+ params = S("<name> (<privilege> [, <privilege2> [<...>]] | all)"),
+ description = S("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)"
+ return false, S("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",
+ params = S("<privilege> [, <privilege2> [<...>]] | all"),
+ description = S("Grant privileges to yourself"),
func = function(name, param)
if param == "" then
- return false, "Invalid parameters (see /help grantme)"
+ return false, S("Invalid parameters (see /help grantme).")
end
return handle_grant_command(name, name, param)
end,
@@ -256,23 +295,20 @@ core.register_chatcommand("grantme", {
local function handle_revoke_command(caller, revokename, revokeprivstr)
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."
+ return false, S("Your privileges are insufficient.")
end
if not core.get_auth_handler().get_auth(revokename) then
- return false, "Player " .. revokename .. " does not exist."
+ return false, S("Player @1 does not exist.", revokename)
end
- local revokeprivs = core.string_to_privs(revokeprivstr)
local privs = core.get_player_privs(revokename)
- local basic_privs =
- core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
- for priv, _ in pairs(revokeprivs) do
- if not basic_privs[priv] and not caller_privs.privs then
- return false, "Your privileges are insufficient."
- end
- end
+ local revokeprivs = core.string_to_privs(revokeprivstr)
+ local is_singleplayer = core.is_singleplayer()
+ local is_admin = not is_singleplayer
+ and revokename == core.settings:get("name")
+ and revokename ~= ""
if revokeprivstr == "all" then
revokeprivs = privs
privs = {}
@@ -282,53 +318,99 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
end
end
+ local privs_unknown = ""
+ local basic_privs =
+ core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
+ local irrevokable = {}
+ local has_irrevokable_priv = false
+ for priv, _ in pairs(revokeprivs) do
+ if not basic_privs[priv] and not caller_privs.privs then
+ return false, S("Your privileges are insufficient. "..
+ "'@1' only allows you to revoke: @2",
+ "basic_privs",
+ core.privs_to_string(basic_privs, ', '))
+ end
+ local def = core.registered_privileges[priv]
+ if not def then
+ privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
+ elseif is_singleplayer and def.give_to_singleplayer then
+ irrevokable[priv] = true
+ elseif is_admin and def.give_to_admin then
+ irrevokable[priv] = true
+ end
+ end
+ for priv, _ in pairs(irrevokable) do
+ revokeprivs[priv] = nil
+ has_irrevokable_priv = true
+ end
+ if privs_unknown ~= "" then
+ return false, privs_unknown
+ end
+ if has_irrevokable_priv then
+ if is_singleplayer then
+ core.chat_send_player(caller,
+ S("Note: Cannot revoke in singleplayer: @1",
+ core.privs_to_string(irrevokable, ', ')))
+ elseif is_admin then
+ core.chat_send_player(caller,
+ S("Note: Cannot revoke from admin: @1",
+ core.privs_to_string(irrevokable, ', ')))
+ end
+ end
+
+ local revokecount = 0
+
+ core.set_player_privs(revokename, privs)
for priv, _ in pairs(revokeprivs) do
-- call the on_revoke callbacks
core.run_priv_callbacks(revokename, priv, caller, "revoke")
+ revokecount = revokecount + 1
+ end
+ local new_privs = core.get_player_privs(revokename)
+
+ if revokecount == 0 then
+ return false, S("No privileges were revoked.")
end
- core.set_player_privs(revokename, privs)
core.log("action", caller..' revoked ('
..core.privs_to_string(revokeprivs, ', ')
..') privileges from '..revokename)
if revokename ~= caller then
- core.chat_send_player(revokename, caller
- .. " revoked privileges from you: "
- .. core.privs_to_string(revokeprivs, ' '))
+ core.chat_send_player(revokename,
+ S("@1 revoked privileges from you: @2", caller,
+ core.privs_to_string(revokeprivs, ', ')))
end
- return true, "Privileges of " .. revokename .. ": "
- .. core.privs_to_string(
- core.get_player_privs(revokename), ' ')
+ return true, privileges_of(revokename, new_privs)
end
core.register_chatcommand("revoke", {
- params = "<name> (<privilege> | all)",
- description = "Remove privileges from player",
+ params = S("<name> (<privilege> [, <privilege2> [<...>]] | all)"),
+ description = S("Remove privileges from player"),
privs = {},
func = function(name, param)
local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
if not revokename or not revokeprivstr then
- return false, "Invalid parameters (see /help revoke)"
+ return false, S("Invalid parameters (see /help revoke).")
end
return handle_revoke_command(name, revokename, revokeprivstr)
end,
})
core.register_chatcommand("revokeme", {
- params = "<privilege> | all",
- description = "Revoke privileges from yourself",
+ params = S("<privilege> [, <privilege2> [<...>]] | all"),
+ description = S("Revoke privileges from yourself"),
privs = {},
func = function(name, param)
if param == "" then
- return false, "Invalid parameters (see /help revokeme)"
+ return false, S("Invalid parameters (see /help revokeme).")
end
return handle_revoke_command(name, name, param)
end,
})
core.register_chatcommand("setpassword", {
- params = "<name> <password>",
- description = "Set player's password",
+ params = S("<name> <password>"),
+ description = S("Set player's password"),
privs = {password=true},
func = function(name, param)
local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
@@ -338,207 +420,197 @@ core.register_chatcommand("setpassword", {
end
if not toname then
- return false, "Name field required"
+ return false, S("Name field required.")
end
- local act_str_past, act_str_pres
+ local msg_chat, msg_log, msg_ret
if not raw_password then
core.set_player_password(toname, "")
- act_str_past = "cleared"
- act_str_pres = "clears"
+ msg_chat = S("Your password was cleared by @1.", name)
+ msg_log = name .. " clears password of " .. toname .. "."
+ msg_ret = S("Password of player \"@1\" cleared.", toname)
else
core.set_player_password(toname,
core.get_password_hash(toname,
raw_password))
- act_str_past = "set"
- act_str_pres = "sets"
+ msg_chat = S("Your password was set by @1.", name)
+ msg_log = name .. " sets password of " .. toname .. "."
+ msg_ret = S("Password of player \"@1\" set.", toname)
end
if toname ~= name then
- core.chat_send_player(toname, "Your password was "
- .. act_str_past .. " by " .. name)
+ core.chat_send_player(toname, msg_chat)
end
- core.log("action", name .. " " .. act_str_pres ..
- " password of " .. toname .. ".")
+ core.log("action", msg_log)
- return true, "Password of player \"" .. toname .. "\" " .. act_str_past
+ return true, msg_ret
end,
})
core.register_chatcommand("clearpassword", {
- params = "<name>",
- description = "Set empty password for a player",
+ params = S("<name>"),
+ description = S("Set empty password for a player"),
privs = {password=true},
func = function(name, param)
local toname = param
if toname == "" then
- return false, "Name field required"
+ return false, S("Name field required.")
end
core.set_player_password(toname, '')
core.log("action", name .. " clears password of " .. toname .. ".")
- return true, "Password of player \"" .. toname .. "\" cleared"
+ return true, S("Password of player \"@1\" cleared.", toname)
end,
})
core.register_chatcommand("auth_reload", {
params = "",
- description = "Reload authentication data",
+ description = S("Reload authentication data"),
privs = {server=true},
func = function(name, param)
local done = core.auth_reload()
- return done, (done and "Done." or "Failed.")
+ return done, (done and S("Done.") or S("Failed."))
end,
})
core.register_chatcommand("remove_player", {
- params = "<name>",
- description = "Remove a player's data",
+ params = S("<name>"),
+ description = S("Remove a player's data"),
privs = {server=true},
func = function(name, param)
local toname = param
if toname == "" then
- return false, "Name field required"
+ return false, S("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."
+ return true, S("Player \"@1\" removed.", toname)
elseif rc == 1 then
- return true, "No such player \"" .. toname .. "\" to remove."
+ return true, S("No such player \"@1\" to remove.", toname)
elseif rc == 2 then
- return true, "Player \"" .. toname .. "\" is connected, cannot remove."
+ return true, S("Player \"@1\" is connected, cannot remove.", toname)
end
- return false, "Unhandled remove_player return code " .. rc .. ""
+ return false, S("Unhandled remove_player return code @1.", tostring(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
+
+-- pos may be a non-integer position
+local function find_free_position_near(pos)
+ local tries = {
+ vector.new( 1, 0, 0),
+ vector.new(-1, 0, 0),
+ vector.new( 0, 0, 1),
+ vector.new( 0, 0, -1),
+ }
+ for _, d in ipairs(tries) do
+ local p = vector.add(pos, d)
+ local n = core.get_node_or_nil(p)
+ if n then
+ local def = core.registered_nodes[n.name]
+ if def and not def.walkable then
+ return p
end
- return pos, false
end
+ end
+ return pos
+end
+
+-- Teleports player <name> to <p> if possible
+local function teleport_to_pos(name, p)
+ local lm = 31007 -- equals MAX_MAP_GENERATION_LIMIT in C++
+ 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, S("Cannot teleport out of map bounds!")
+ end
+ local teleportee = core.get_player_by_name(name)
+ if not teleportee then
+ return false, S("Cannot get player with name @1.", name)
+ end
+ if teleportee:get_attach() then
+ return false, S("Cannot teleport, @1 " ..
+ "is attached to an object!", name)
+ end
+ teleportee:set_pos(p)
+ return true, S("Teleporting @1 to @2.", name, core.pos_to_string(p, 1))
+end
+-- Teleports player <name> next to player <target_name> if possible
+local function teleport_to_player(name, target_name)
+ if name == target_name then
+ return false, S("One does not teleport to oneself.")
+ end
+ local teleportee = core.get_player_by_name(name)
+ if not teleportee then
+ return false, S("Cannot get teleportee with name @1.", name)
+ end
+ if teleportee:get_attach() then
+ return false, S("Cannot teleport, @1 " ..
+ "is attached to an object!", name)
+ end
+ local target = core.get_player_by_name(target_name)
+ if not target then
+ return false, S("Cannot get target player with name @1.", target_name)
+ end
+ local p = find_free_position_near(target:get_pos())
+ teleportee:set_pos(p)
+ return true, S("Teleporting @1 to @2 at @3.", name, target_name,
+ core.pos_to_string(p, 1))
+end
+
+core.register_chatcommand("teleport", {
+ params = S("<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>"),
+ description = S("Teleport to position or player"),
+ privs = {teleport=true},
+ func = function(name, param)
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)
+ p.x, p.y, p.z = param:match("^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+ p = vector.apply(p, tonumber)
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
- local teleportee = core.get_player_by_name(name)
- if teleportee then
- if teleportee:get_attach() then
- return false, "Can't teleport, you're attached to an object!"
- end
- teleportee:set_pos(p)
- return true, "Teleporting to "..core.pos_to_string(p)
- end
+ return teleport_to_pos(name, p)
end
local target_name = param:match("^([^ ]+)$")
- local teleportee = core.get_player_by_name(name)
-
- p = nil
if target_name then
- local target = core.get_player_by_name(target_name)
- if target then
- p = target:get_pos()
- end
+ return teleport_to_player(name, target_name)
end
- if teleportee and p then
- if teleportee:get_attach() then
- return false, "Can't teleport, you're attached to an object!"
- end
- p = find_free_position_near(p)
- teleportee:set_pos(p)
- return true, "Teleporting to " .. target_name
- .. " at "..core.pos_to_string(p)
- end
+ local has_bring_priv = core.check_player_privs(name, {bring=true})
+ local missing_bring_msg = S("You don't have permission to teleport " ..
+ "other players (missing privilege: @1).", "bring")
- 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
-
- teleportee = nil
- p = {}
local teleportee_name
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
- if teleportee:get_attach() then
- return false, "Can't teleport, player is attached to an object!"
+ p = vector.apply(p, tonumber)
+ if teleportee_name and p.x and p.y and p.z then
+ if not has_bring_priv then
+ return false, missing_bring_msg
end
- teleportee:set_pos(p)
- return true, "Teleporting " .. teleportee_name
- .. " to " .. core.pos_to_string(p)
+ return teleport_to_pos(teleportee_name, p)
end
- teleportee = nil
- p = 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
- if teleportee:get_attach() then
- return false, "Can't teleport, player is attached to an object!"
+ if teleportee_name and target_name then
+ if not has_bring_priv then
+ return false, missing_bring_msg
end
- p = find_free_position_near(p)
- teleportee:set_pos(p)
- return true, "Teleporting " .. teleportee_name
- .. " to " .. target_name
- .. " at " .. core.pos_to_string(p)
+ return teleport_to_player(teleportee_name, target_name)
end
- return false, 'Invalid parameters ("' .. param
- .. '") or player not found (see /help teleport)'
+ return false
end,
})
core.register_chatcommand("set", {
- params = "([-n] <name> <value>) | <name>",
- description = "Set or read server configuration setting",
+ params = S("([-n] <name> <value>) | <name>"),
+ description = S("Set or read server configuration setting"),
privs = {server=true},
func = function(name, param)
local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
@@ -550,22 +622,23 @@ core.register_chatcommand("set", {
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."
+ return false, S("Failed. Use '/set -n <name> <value>' "
+ .. "to create a new setting.")
end
core.settings:set(setname, setvalue)
- return true, setname .. " = " .. setvalue
+ return true, S("@1 = @2", setname, setvalue)
end
setname = string.match(param, "([^ ]+)")
if setname then
setvalue = core.settings:get(setname)
if not setvalue then
- setvalue = "<not set>"
+ setvalue = S("<not set>")
end
- return true, setname .. " = " .. setvalue
+ return true, S("@1 = @2", setname, setvalue)
end
- return false, "Invalid parameters (see /help set)."
+ return false, S("Invalid parameters (see /help set).")
end,
})
@@ -578,26 +651,27 @@ local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
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))
+ S("Finished emerging @1 blocks in @2ms.",
+ ctx.total_blocks,
+ string.format("%.2f", (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%%)",
+ S("emergeblocks update: @1/@2 blocks emerged (@3%)",
ctx.current_blocks, ctx.total_blocks,
- (ctx.current_blocks / ctx.total_blocks) * 100))
+ string.format("%.1f", (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)",
+ params = S("(here [<radius>]) | (<pos1> <pos2>)"),
+ description = S("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)
@@ -615,15 +689,15 @@ core.register_chatcommand("emergeblocks", {
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)
+ return true, S("Started emerge of area ranging from @1 to @2.",
+ core.pos_to_string(p1, 1), 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)",
+ params = S("(here [<radius>]) | (<pos1> <pos2>)"),
+ description = S("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)
@@ -632,18 +706,20 @@ core.register_chatcommand("deleteblocks", {
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)
+ return true, S("Successfully cleared area "
+ .. "ranging from @1 to @2.",
+ core.pos_to_string(p1, 1), core.pos_to_string(p2, 1))
else
- return false, "Failed to clear one or more blocks in area"
+ return false, S("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)",
+ params = S("(here [<radius>]) | (<pos1> <pos2>)"),
+ description = S("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)
@@ -652,20 +728,26 @@ core.register_chatcommand("fixlight", {
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)
+ return true, S("Successfully reset light in the area "
+ .. "ranging from @1 to @2.",
+ core.pos_to_string(p1, 1), core.pos_to_string(p2, 1))
else
- return false, "Failed to load one or more blocks in area"
+ return false, S("Failed to load one or more blocks in area.")
end
end,
})
core.register_chatcommand("mods", {
params = "",
- description = "List mods installed on the server",
+ description = S("List mods installed on the server"),
privs = {},
func = function(name, param)
- return true, table.concat(core.get_modnames(), ", ")
+ local mods = core.get_modnames()
+ if #mods == 0 then
+ return true, S("No mods installed.")
+ else
+ return true, table.concat(core.get_modnames(), ", ")
+ end
end,
})
@@ -674,117 +756,136 @@ local function handle_give_command(cmd, giver, receiver, stackstring)
.. ', stackstring="' .. stackstring .. '"')
local itemstack = ItemStack(stackstring)
if itemstack:is_empty() then
- return false, "Cannot give an empty item"
+ return false, S("Cannot give an empty item.")
elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
- return false, "Cannot give an unknown item"
+ return false, S("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"
+ return false, S("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"
+ return false, S("@1 is not a known player.", receiver)
end
local leftover = receiverref:get_inventory():add_item("main", itemstack)
local partiality
if leftover:is_empty() then
- partiality = ""
+ partiality = nil
elseif leftover:get_count() == itemstack:get_count() then
- partiality = "could not be "
+ partiality = false
else
- partiality = "partially "
+ partiality = true
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()
+ local msg
+ if partiality == true then
+ msg = S("@1 partially added to inventory.", stackstring)
+ elseif partiality == false then
+ msg = S("@1 could not be added to inventory.", stackstring)
+ else
+ msg = S("@1 added to inventory.", stackstring)
+ end
if giver == receiver then
- local msg = "%q %sadded to inventory."
- return true, msg:format(stackstring, partiality)
+ return true, msg
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)
+ core.chat_send_player(receiver, msg)
+ local msg_other
+ if partiality == true then
+ msg_other = S("@1 partially added to inventory of @2.",
+ stackstring, receiver)
+ elseif partiality == false then
+ msg_other = S("@1 could not be added to inventory of @2.",
+ stackstring, receiver)
+ else
+ msg_other = S("@1 added to inventory of @2.",
+ stackstring, receiver)
+ end
+ return true, msg_other
end
end
core.register_chatcommand("give", {
- params = "<name> <ItemString> [<count> [<wear>]]",
- description = "Give item to player",
+ params = S("<name> <ItemString> [<count> [<wear>]]"),
+ description = S("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"
+ return false, S("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",
+ params = S("<ItemString> [<count> [<wear>]]"),
+ description = S("Give item to yourself"),
privs = {give=true},
func = function(name, param)
local itemstring = string.match(param, "(.+)$")
if not itemstring then
- return false, "ItemString required"
+ return false, S("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",
+ params = S("<EntityName> [<X>,<Y>,<Z>]"),
+ description = S("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"
+ return false, S("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"
+ return false, S("Unable to spawn entity, player is nil.")
end
if not core.registered_entities[entityname] then
- return false, "Cannot spawn an unknown entity"
+ return false, S("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 .. "')"
+ return false, S("Invalid parameters (@1).", param)
end
end
p.y = p.y + 1
local obj = core.add_entity(p, entityname)
- local msg = obj and "%q spawned." or "%q failed to spawn."
- return true, msg:format(entityname)
+ if obj then
+ return true, S("@1 spawned.", entityname)
+ else
+ return true, S("@1 failed to spawn.", entityname)
+ end
end,
})
core.register_chatcommand("pulverize", {
params = "",
- description = "Destroy item in hand",
+ description = S("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."
+ return false, S("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."
+ return false, S("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."
+ return true, S("An item was pulverized.")
end,
})
@@ -800,14 +901,15 @@ core.register_on_punchnode(function(pos, node, puncher)
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. Set <seconds> to inf for no time limit",
+ params = S("[<range>] [<seconds>] [<limit>]"),
+ description = S("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. "
+ .. "Set <seconds> to inf for no time limit"),
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
- return false, "Rollback functions are disabled."
+ return false, S("Rollback functions are disabled.")
end
local range, seconds, limit =
param:match("(%d+) *(%d*) *(%d*)")
@@ -815,30 +917,30 @@ core.register_chatcommand("rollback_check", {
seconds = tonumber(seconds) or 86400
limit = tonumber(limit) or 5
if limit > 100 then
- return false, "That limit is too high!"
+ return false, S("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) .. "...")
+ core.chat_send_player(name, S("Checking @1 ...", 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")
+ core.chat_send_player(name, S("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")
+ core.chat_send_player(name,
+ S("Nobody has touched the specified "
+ .. "location in @1 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(
+ S("@1 @2 @3 -> @4 @5 seconds ago.",
core.pos_to_string(action.pos),
action.actor,
action.oldnode.name,
@@ -847,189 +949,235 @@ core.register_chatcommand("rollback_check", {
end
end
- return true, "Punch a node (range=" .. range .. ", seconds="
- .. seconds .. "s, limit=" .. limit .. ")"
+ return true, S("Punch a node (range=@1, seconds=@2, limit=@3).",
+ range, seconds, limit)
end,
})
core.register_chatcommand("rollback", {
- params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
- description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
+ params = S("(<name> [<seconds>]) | (:<actor> [<seconds>])"),
+ description = S("Revert actions of a player. "
+ .. "Default for <seconds> is 60. "
+ .. "Set <seconds> to inf for no time limit"),
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
- return false, "Rollback functions are disabled."
+ return false, S("Rollback functions are disabled.")
end
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
+ local rev_msg
if not target_name then
local player_name
player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
if not player_name then
- return false, "Invalid parameters. See /help rollback"
- .. " and /help rollback_check."
+ return false, S("Invalid parameters. "
+ .. "See /help rollback and "
+ .. "/help rollback_check.")
end
+ seconds = tonumber(seconds) or 60
target_name = "player:"..player_name
+ rev_msg = S("Reverting actions of player '@1' since @2 seconds.",
+ player_name, seconds)
+ else
+ seconds = tonumber(seconds) or 60
+ rev_msg = S("Reverting actions of @1 since @2 seconds.",
+ target_name, seconds)
end
- seconds = tonumber(seconds) or 60
- core.chat_send_player(name, "Reverting actions of "
- .. target_name .. " since "
- .. seconds .. " seconds.")
+ core.chat_send_player(name, rev_msg)
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"
+ response = S("(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.")
+ if success then
+ response = response .. S("Reverting actions succeeded.")
+ else
+ response = response .. S("Reverting actions FAILED.")
+ end
return success, response
end,
})
core.register_chatcommand("status", {
- description = "Show server status",
+ description = S("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"
+ return false, S("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",
+ params = S("[<0..23>:<0..59> | <0..24000>]"),
+ description = S("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)
+ return true, S("Current time is @1:@2.",
+ string.format("%d", hour),
+ string.format("%02d", 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)."
+ return false, S("You don't have permission to run "
+ .. "this command (missing privilege: @1).", "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."
+ local new_time = tonumber(param) or -1
+ if new_time ~= new_time or new_time < 0 or new_time > 24000 then
+ return false, S("Invalid time (must be between 0 and 24000).")
end
- -- Backward compatibility.
- core.set_timeofday((new_time % 24000) / 24000)
+ core.set_timeofday(new_time / 24000)
core.log("action", name .. " sets time to " .. new_time)
- return true, "Time of day changed."
+ return true, S("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)."
+ return false, S("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)."
+ return false, S("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."
+ return true, S("Time of day changed.")
end,
})
core.register_chatcommand("days", {
- description = "Show day count since world creation",
+ description = S("Show day count since world creation"),
func = function(name, param)
- return true, "Current day is " .. core.get_day_count()
+ return true, S("Current day is @1.", core.get_day_count())
end
})
+local function parse_shutdown_param(param)
+ local delay, reconnect, message
+ local one, two, three
+ one, two, three = param:match("^(%S+) +(%-r) +(.*)")
+ if one and two and three then
+ -- 3 arguments: delay, reconnect and message
+ return one, two, three
+ end
+ -- 2 arguments
+ one, two = param:match("^(%S+) +(.*)")
+ if one and two then
+ if tonumber(one) then
+ delay = one
+ if two == "-r" then
+ reconnect = two
+ else
+ message = two
+ end
+ elseif one == "-r" then
+ reconnect, message = one, two
+ end
+ return delay, reconnect, message
+ end
+ -- 1 argument
+ one = param:match("(.*)")
+ if tonumber(one) then
+ delay = one
+ elseif one == "-r" then
+ reconnect = one
+ else
+ message = one
+ end
+ return delay, reconnect, message
+end
+
core.register_chatcommand("shutdown", {
- params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
- description = "Shutdown server (-1 cancels a delayed shutdown)",
+ params = S("[<delay_in_seconds> | -1] [-r] [<message>]"),
+ description = S("Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)"),
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+)(.*)")
+ local delay, reconnect, message = parse_shutdown_param(param)
+ local bool_reconnect = reconnect == "-r"
+ if not message then
+ message = ""
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).")
+ core.chat_send_all("*** "..S("Server shutting down (operator request)."))
end
- core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
+ core.request_shutdown(message:trim(), bool_reconnect, delay)
return true
end,
})
core.register_chatcommand("ban", {
- params = "[<name>]",
- description = "Ban the IP of a player or show the ban list",
+ params = S("[<name>]"),
+ description = S("Ban the IP of a player or show the 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."
+ return true, S("The ban list is empty.")
else
- return true, "Ban list: " .. ban_list
+ return true, S("Ban list: @1", ban_list)
end
end
if not core.get_player_by_name(param) then
- return false, "Player is not online."
+ return false, S("Player is not online.")
end
if not core.ban_player(param) then
- return false, "Failed to ban player."
+ return false, S("Failed to ban player.")
end
local desc = core.get_ban_description(param)
core.log("action", name .. " bans " .. desc .. ".")
- return true, "Banned " .. desc .. "."
+ return true, S("Banned @1.", desc)
end,
})
core.register_chatcommand("unban", {
- params = "<name> | <IP_address>",
- description = "Remove IP ban belonging to a player/IP",
+ params = S("<name> | <IP_address>"),
+ description = S("Remove IP ban belonging to a player/IP"),
privs = {ban=true},
func = function(name, param)
if not core.unban_player_or_ip(param) then
- return false, "Failed to unban player/IP."
+ return false, S("Failed to unban player/IP.")
end
core.log("action", name .. " unbans " .. param)
- return true, "Unbanned " .. param
+ return true, S("Unbanned @1.", param)
end,
})
core.register_chatcommand("kick", {
- params = "<name> [<reason>]",
- description = "Kick a player",
+ params = S("<name> [<reason>]"),
+ description = S("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
+ return false, S("Failed to kick player @1.", 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
+ return true, S("Kicked @1.", tokick)
end,
})
core.register_chatcommand("clearobjects", {
- params = "[full | quick]",
- description = "Clear all objects in world",
+ params = S("[full | quick]"),
+ description = S("Clear all objects in world"),
privs = {server=true},
func = function(name, param)
local options = {}
@@ -1038,45 +1186,44 @@ core.register_chatcommand("clearobjects", {
elseif param == "full" then
options.mode = "full"
else
- return false, "Invalid usage, see /help clearobjects."
+ return false, S("Invalid usage, see /help clearobjects.")
end
- core.log("action", name .. " clears all objects ("
+ core.log("action", name .. " clears objects ("
.. options.mode .. " mode).")
- core.chat_send_all("Clearing all objects. This may take a long time."
- .. " You may experience a timeout. (by "
- .. name .. ")")
+ if options.mode == "full" then
+ core.chat_send_all(S("Clearing all objects. This may take a long time. "
+ .. "You may experience a timeout. (by @1)", name))
+ end
core.clear_objects(options)
core.log("action", "Object clearing done.")
- core.chat_send_all("*** Cleared all objects.")
+ core.chat_send_all("*** "..S("Cleared all objects."))
return true
end,
})
core.register_chatcommand("msg", {
- params = "<name> <message>",
- description = "Send a direct message to a player",
+ params = S("<name> <message>"),
+ description = S("Send a direct message to a player"),
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."
+ return false, S("Invalid usage, see /help msg.")
end
if not core.get_player_by_name(sendto) then
- return false, "The player " .. sendto
- .. " is not online."
+ return false, S("The player @1 is not online.", sendto)
end
core.log("action", "DM from " .. name .. " to " .. sendto
.. ": " .. message)
- core.chat_send_player(sendto, "DM from " .. name .. ": "
- .. message)
- return true, "Message sent."
+ core.chat_send_player(sendto, S("DM from @1: @2", name, message))
+ return true, S("Message sent.")
end,
})
core.register_chatcommand("last-login", {
- params = "[<name>]",
- description = "Get the last login time of a player or yourself",
+ params = S("[<name>]"),
+ description = S("Get the last login time of a player or yourself"),
func = function(name, param)
if param == "" then
param = name
@@ -1084,25 +1231,27 @@ core.register_chatcommand("last-login", {
local pauth = core.get_auth_handler().get_auth(param)
if pauth and pauth.last_login and pauth.last_login ~= -1 then
-- Time in UTC, ISO 8601 format
- return true, param.."'s last login time was " ..
- os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
+ return true, S("@1's last login time was @2.",
+ param,
+ os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login))
end
- return false, param.."'s last login time is unknown"
+ return false, S("@1's last login time is unknown.", param)
end,
})
core.register_chatcommand("clearinv", {
- params = "[<name>]",
- description = "Clear the inventory of yourself or another player",
+ params = S("[<name>]"),
+ description = S("Clear the inventory of yourself or another player"),
func = function(name, param)
local player
if param and param ~= "" and param ~= name then
if not core.check_player_privs(name, {server=true}) then
- return false, "You don't have permission"
- .. " to clear another player's inventory (missing privilege: server)"
+ return false, S("You don't have permission to "
+ .. "clear another player's inventory "
+ .. "(missing privilege: @1).", "server")
end
player = core.get_player_by_name(param)
- core.chat_send_player(param, name.." cleared your inventory.")
+ core.chat_send_player(param, S("@1 cleared your inventory.", name))
else
player = core.get_player_by_name(name)
end
@@ -1112,25 +1261,25 @@ core.register_chatcommand("clearinv", {
player:get_inventory():set_list("craft", {})
player:get_inventory():set_list("craftpreview", {})
core.log("action", name.." clears "..player:get_player_name().."'s inventory")
- return true, "Cleared "..player:get_player_name().."'s inventory."
+ return true, S("Cleared @1's inventory.", player:get_player_name())
else
- return false, "Player must be online to clear inventory!"
+ return false, S("Player must be online to clear inventory!")
end
end,
})
local function handle_kill_command(killer, victim)
if core.settings:get_bool("enable_damage") == false then
- return false, "Players can't be killed, damage has been disabled."
+ return false, S("Players can't be killed, damage has been disabled.")
end
local victimref = core.get_player_by_name(victim)
if victimref == nil then
- return false, string.format("Player %s is not online.", victim)
+ return false, S("Player @1 is not online.", victim)
elseif victimref:get_hp() <= 0 then
if killer == victim then
- return false, "You are already dead."
+ return false, S("You are already dead.")
else
- return false, string.format("%s is already dead.", victim)
+ return false, S("@1 is already dead.", victim)
end
end
if not killer == victim then
@@ -1138,12 +1287,12 @@ local function handle_kill_command(killer, victim)
end
-- Kill victim
victimref:set_hp(0)
- return true, string.format("%s has been killed.", victim)
+ return true, S("@1 has been killed.", victim)
end
core.register_chatcommand("kill", {
- params = "[<name>]",
- description = "Kill player or yourself",
+ params = S("[<name>]"),
+ description = S("Kill player or yourself"),
privs = {server=true},
func = function(name, param)
return handle_kill_command(name, param == "" and name or param)
diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua
index 057d0d0ed..29cb56aae 100644
--- a/builtin/game/falling.lua
+++ b/builtin/game/falling.lua
@@ -39,7 +39,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
core.register_entity(":__builtin:falling_node", {
initial_properties = {
visual = "item",
- visual_size = {x = SCALE, y = SCALE, z = SCALE},
+ visual_size = vector.new(SCALE, SCALE, SCALE),
textures = {},
physical = true,
is_visible = false,
@@ -84,9 +84,6 @@ core.register_entity(":__builtin:falling_node", {
local textures
if def.tiles and def.tiles[1] then
local tile = def.tiles[1]
- if def.drawtype == "torchlike" and def.paramtype2 ~= "wallmounted" then
- tile = def.tiles[2] or def.tiles[1]
- end
if type(tile) == "table" then
tile = tile.name
end
@@ -99,7 +96,7 @@ core.register_entity(":__builtin:falling_node", {
local vsize
if def.visual_scale then
local s = def.visual_scale
- vsize = {x = s, y = s, z = s}
+ vsize = vector.new(s, s, s)
end
self.object:set_properties({
is_visible = true,
@@ -114,15 +111,21 @@ core.register_entity(":__builtin:falling_node", {
itemstring = core.itemstring_with_palette(itemstring, node.param2)
end
-- FIXME: solution needed for paramtype2 == "leveled"
- local vsize
- if def.visual_scale then
- local s = def.visual_scale * SCALE
- vsize = {x = s, y = s, z = s}
+ -- Calculate size of falling node
+ local s = {}
+ s.x = (def.visual_scale or 1) * SCALE
+ s.y = s.x
+ s.z = s.x
+ -- Compensate for wield_scale
+ if def.wield_scale then
+ s.x = s.x / def.wield_scale.x
+ s.y = s.y / def.wield_scale.y
+ s.z = s.z / def.wield_scale.z
end
self.object:set_properties({
is_visible = true,
wield_item = itemstring,
- visual_size = vsize,
+ visual_size = s,
glow = def.light_source,
})
end
@@ -147,11 +150,7 @@ core.register_entity(":__builtin:falling_node", {
-- Rotate entity
if def.drawtype == "torchlike" then
- if def.paramtype2 == "wallmounted" then
- self.object:set_yaw(math.pi*0.25)
- else
- self.object:set_yaw(-math.pi*0.25)
- end
+ self.object:set_yaw(math.pi*0.25)
elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
and (def.wield_image == "" or def.wield_image == nil))
or def.drawtype == "signlike"
@@ -165,8 +164,13 @@ core.register_entity(":__builtin:falling_node", {
if euler then
self.object:set_rotation(euler)
end
- elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") then
+ elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and
+ (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then
local rot = node.param2 % 8
+ if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then
+ -- Change rotation to "floor" by default for non-wallmounted paramtype2
+ rot = 1
+ end
local pitch, yaw, roll = 0, 0, 0
if def.drawtype == "nodebox" or def.drawtype == "mesh" then
if rot == 0 then
@@ -208,6 +212,14 @@ core.register_entity(":__builtin:falling_node", {
end
end
self.object:set_rotation({x=pitch, y=yaw, z=roll})
+ elseif (def.drawtype == "mesh" and def.paramtype2 == "degrotate") then
+ local p2 = (node.param2 - (def.place_param2 or 0)) % 240
+ local yaw = (p2 / 240) * (math.pi * 2)
+ self.object:set_yaw(yaw)
+ elseif (def.drawtype == "mesh" and def.paramtype2 == "colordegrotate") then
+ local p2 = (node.param2 % 32 - (def.place_param2 or 0) % 32) % 24
+ local yaw = (p2 / 24) * (math.pi * 2)
+ self.object:set_yaw(yaw)
end
end
end,
@@ -222,7 +234,7 @@ core.register_entity(":__builtin:falling_node", {
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
- self.object:set_acceleration({x = 0, y = -gravity, z = 0})
+ self.object:set_acceleration(vector.new(0, -gravity, 0))
local ds = core.deserialize(staticdata)
if ds and ds.node then
@@ -298,7 +310,7 @@ core.register_entity(":__builtin:falling_node", {
if self.floats then
local pos = self.object:get_pos()
- local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z})
+ local bcp = pos:offset(0, -0.7, 0):round()
local bcn = core.get_node(bcp)
local bcd = core.registered_nodes[bcn.name]
@@ -339,13 +351,12 @@ core.register_entity(":__builtin:falling_node", {
-- TODO: this hack could be avoided in the future if objects
-- could choose who to collide with
local vel = self.object:get_velocity()
- self.object:set_velocity({
- x = vel.x,
- y = player_collision.old_velocity.y,
- z = vel.z
- })
- self.object:set_pos(vector.add(self.object:get_pos(),
- {x = 0, y = -0.5, z = 0}))
+ self.object:set_velocity(vector.new(
+ vel.x,
+ player_collision.old_velocity.y,
+ vel.z
+ ))
+ self.object:set_pos(self.object:get_pos():offset(0, -0.5, 0))
end
return
elseif bcn.name == "ignore" then
@@ -407,7 +418,7 @@ local function convert_to_falling_node(pos, node)
obj:get_luaentity():set_node(node, metatable)
core.remove_node(pos)
- return true
+ return true, obj
end
function core.spawn_falling_node(pos)
@@ -425,7 +436,7 @@ local function drop_attached_node(p)
if def and def.preserve_metadata then
local oldmeta = core.get_meta(p):to_table().fields
-- Copy pos and node because the callback can modify them.
- local pos_copy = {x=p.x, y=p.y, z=p.z}
+ local pos_copy = vector.new(p)
local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@@ -450,14 +461,14 @@ end
function builtin_shared.check_attached_node(p, n)
local def = core.registered_nodes[n.name]
- local d = {x = 0, y = 0, z = 0}
+ local d = vector.new()
if def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted" then
-- The fallback vector here is in case 'wallmounted to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for wallmounted.
-- The fallback vector corresponds to param2 = 0.
- d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0}
+ d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
else
d.y = -1
end
@@ -477,7 +488,7 @@ end
function core.check_single_for_falling(p)
local n = core.get_node(p)
if core.get_item_group(n.name, "falling_node") ~= 0 then
- local p_bottom = {x = p.x, y = p.y - 1, z = p.z}
+ local p_bottom = vector.offset(p, 0, -1, 0)
-- Only spawn falling node if node below is loaded
local n_bottom = core.get_node_or_nil(p_bottom)
local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
@@ -516,17 +527,17 @@ end
-- Down first as likely case, but always before self. The same with sides.
-- Up must come last, so that things above self will also fall all at once.
local check_for_falling_neighbors = {
- {x = -1, y = -1, z = 0},
- {x = 1, y = -1, z = 0},
- {x = 0, y = -1, z = -1},
- {x = 0, y = -1, z = 1},
- {x = 0, y = -1, z = 0},
- {x = -1, y = 0, z = 0},
- {x = 1, y = 0, z = 0},
- {x = 0, y = 0, z = 1},
- {x = 0, y = 0, z = -1},
- {x = 0, y = 0, z = 0},
- {x = 0, y = 1, z = 0},
+ vector.new(-1, -1, 0),
+ vector.new( 1, -1, 0),
+ vector.new( 0, -1, -1),
+ vector.new( 0, -1, 1),
+ vector.new( 0, -1, 0),
+ vector.new(-1, 0, 0),
+ vector.new( 1, 0, 0),
+ vector.new( 0, 0, 1),
+ vector.new( 0, 0, -1),
+ vector.new( 0, 0, 0),
+ vector.new( 0, 1, 0),
}
function core.check_for_falling(p)
diff --git a/builtin/game/features.lua b/builtin/game/features.lua
index 36ff1f0b0..583ef5092 100644
--- a/builtin/game/features.lua
+++ b/builtin/game/features.lua
@@ -19,6 +19,9 @@ core.features = {
object_step_has_moveresult = true,
direct_velocity_on_players = true,
use_texture_alpha_string_modes = true,
+ degrotate_240_steps = true,
+ abm_min_max_y = true,
+ dynamic_add_media_table = true,
}
function core.has_feature(arg)
diff --git a/builtin/game/forceloading.lua b/builtin/game/forceloading.lua
index e1e00920c..8043e5dea 100644
--- a/builtin/game/forceloading.lua
+++ b/builtin/game/forceloading.lua
@@ -86,12 +86,6 @@ local function read_file(filename)
return core.deserialize(t) or {}
end
-local function write_file(filename, table)
- local f = io.open(filename, "w")
- f:write(core.serialize(table))
- f:close()
-end
-
blocks_forceloaded = read_file(wpath.."/force_loaded.txt")
for _, __ in pairs(blocks_forceloaded) do
total_forceloaded = total_forceloaded + 1
@@ -106,7 +100,8 @@ end)
-- persists the currently forceloaded blocks to disk
local function persist_forceloaded_blocks()
- write_file(wpath.."/force_loaded.txt", blocks_forceloaded)
+ local data = core.serialize(blocks_forceloaded)
+ core.safe_file_write(wpath.."/force_loaded.txt", data)
end
-- periodical forceload persistence
diff --git a/builtin/game/init.lua b/builtin/game/init.lua
index 1d62be019..bb007fabd 100644
--- a/builtin/game/init.lua
+++ b/builtin/game/init.lua
@@ -7,8 +7,6 @@ local gamepath = scriptpath .. "game".. DIR_DELIM
-- not exposed to outer context
local builtin_shared = {}
-dofile(commonpath .. "vector.lua")
-
dofile(gamepath .. "constants.lua")
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
dofile(gamepath .. "register.lua")
diff --git a/builtin/game/item.lua b/builtin/game/item.lua
index b68177c22..5a83eafd2 100644
--- a/builtin/game/item.lua
+++ b/builtin/game/item.lua
@@ -92,12 +92,12 @@ end
-- Table of possible dirs
local facedir_to_dir = {
- {x= 0, y=0, z= 1},
- {x= 1, y=0, z= 0},
- {x= 0, y=0, z=-1},
- {x=-1, y=0, z= 0},
- {x= 0, y=-1, z= 0},
- {x= 0, y=1, z= 0},
+ vector.new( 0, 0, 1),
+ vector.new( 1, 0, 0),
+ vector.new( 0, 0, -1),
+ vector.new(-1, 0, 0),
+ vector.new( 0, -1, 0),
+ vector.new( 0, 1, 0),
}
-- Mapping from facedir value to index in facedir_to_dir.
local facedir_to_dir_map = {
@@ -136,12 +136,12 @@ end
-- table of dirs in wallmounted order
local wallmounted_to_dir = {
- [0] = {x = 0, y = 1, z = 0},
- {x = 0, y = -1, z = 0},
- {x = 1, y = 0, z = 0},
- {x = -1, y = 0, z = 0},
- {x = 0, y = 0, z = 1},
- {x = 0, y = 0, z = -1},
+ [0] = vector.new( 0, 1, 0),
+ vector.new( 0, -1, 0),
+ vector.new( 1, 0, 0),
+ vector.new(-1, 0, 0),
+ vector.new( 0, 0, 1),
+ vector.new( 0, 0, -1),
}
function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8]
@@ -152,12 +152,12 @@ function core.dir_to_yaw(dir)
end
function core.yaw_to_dir(yaw)
- return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
+ return vector.new(-math.sin(yaw), 0, math.cos(yaw))
end
function core.is_colored_paramtype(ptype)
return (ptype == "color") or (ptype == "colorfacedir") or
- (ptype == "colorwallmounted")
+ (ptype == "colorwallmounted") or (ptype == "colordegrotate")
end
function core.strip_param2_color(param2, paramtype2)
@@ -168,11 +168,25 @@ function core.strip_param2_color(param2, paramtype2)
param2 = math.floor(param2 / 32) * 32
elseif paramtype2 == "colorwallmounted" then
param2 = math.floor(param2 / 8) * 8
+ elseif paramtype2 == "colordegrotate" then
+ param2 = math.floor(param2 / 32) * 32
end
-- paramtype2 == "color" requires no modification.
return param2
end
+local function has_all_groups(tbl, required_groups)
+ if type(required_groups) == "string" then
+ return (tbl[required_groups] or 0) ~= 0
+ end
+ for _, group in ipairs(required_groups) do
+ if (tbl[group] or 0) == 0 then
+ return false
+ end
+ end
+ return true
+end
+
function core.get_node_drops(node, toolname)
-- Compatibility, if node is string
local nodename = node
@@ -212,7 +226,7 @@ function core.get_node_drops(node, toolname)
if item.rarity ~= nil then
good_rarity = item.rarity < 1 or math.random(item.rarity) == 1
end
- if item.tools ~= nil then
+ if item.tools ~= nil or item.tool_groups ~= nil then
good_tool = false
end
if item.tools ~= nil and toolname then
@@ -227,6 +241,27 @@ function core.get_node_drops(node, toolname)
end
end
end
+ if item.tool_groups ~= nil and toolname then
+ local tooldef = core.registered_items[toolname]
+ if tooldef ~= nil and type(tooldef.groups) == "table" then
+ if type(item.tool_groups) == "string" then
+ -- tool_groups can be a string which specifies the required group
+ good_tool = core.get_item_group(toolname, item.tool_groups) ~= 0
+ else
+ -- tool_groups can be a list of sufficient requirements.
+ -- i.e. if any item in the list can be satisfied then the tool is good
+ assert(type(item.tool_groups) == "table")
+ for _, required_groups in ipairs(item.tool_groups) do
+ -- required_groups can be either a string (a single group),
+ -- or an array of strings where all must be in tooldef.groups
+ good_tool = has_all_groups(tooldef.groups, required_groups)
+ if good_tool then
+ break
+ end
+ end
+ end
+ end
+ end
if good_rarity and good_tool then
got_count = got_count + 1
for _, add_item in ipairs(item.items) do
@@ -288,12 +323,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
end
-- Place above pointed node
- local place_to = {x = above.x, y = above.y, z = above.z}
+ local place_to = vector.new(above)
-- If node under is buildable_to, place into it instead (eg. snow)
if olddef_under.buildable_to then
log("info", "node under is buildable to")
- place_to = {x = under.x, y = under.y, z = under.z}
+ place_to = vector.new(under)
end
if core.is_protected(place_to, playername) then
@@ -313,22 +348,14 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
newnode.param2 = def.place_param2
elseif (def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted") and not param2 then
- local dir = {
- x = under.x - above.x,
- y = under.y - above.y,
- z = under.z - above.z
- }
+ local dir = vector.subtract(under, above)
newnode.param2 = core.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir") and not param2 then
local placer_pos = placer and placer:get_pos()
if placer_pos then
- local dir = {
- x = above.x - placer_pos.x,
- y = above.y - placer_pos.y,
- z = above.z - placer_pos.z
- }
+ local dir = vector.subtract(above, placer_pos)
newnode.param2 = core.dir_to_facedir(dir)
log("info", "facedir: " .. newnode.param2)
end
@@ -345,6 +372,8 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
color_divisor = 8
elseif def.paramtype2 == "colorfacedir" then
color_divisor = 32
+ elseif def.paramtype2 == "colordegrotate" then
+ color_divisor = 32
end
if color_divisor then
local color = math.floor(metatable.palette_index / color_divisor)
@@ -380,7 +409,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
-- Run callback
if def.after_place_node and not prevent_after_place then
-- Deepcopy place_to and pointed_thing because callback can modify it
- local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
+ local place_to_copy = vector.new(place_to)
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if def.after_place_node(place_to_copy, placer, itemstack,
pointed_thing_copy) then
@@ -391,7 +420,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
-- Run script hook
for _, callback in ipairs(core.registered_on_placenodes) do
-- Deepcopy pos, node and pointed_thing because callback can modify them
- local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
+ local place_to_copy = vector.new(place_to)
local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
@@ -470,34 +499,41 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
return result
end
end
+ -- read definition before potentially emptying the stack
local def = itemstack:get_definition()
- if itemstack:take_item() ~= nil then
- user:set_hp(user:get_hp() + hp_change)
-
- if def and def.sound and def.sound.eat then
- core.sound_play(def.sound.eat, {
- pos = user:get_pos(),
- max_hear_distance = 16
- }, true)
- end
+ if itemstack:take_item():is_empty() then
+ return itemstack
+ end
+
+ if def and def.sound and def.sound.eat then
+ core.sound_play(def.sound.eat, {
+ pos = user:get_pos(),
+ max_hear_distance = 16
+ }, true)
+ end
- if replace_with_item then
- if itemstack:is_empty() then
- itemstack:add_item(replace_with_item)
+ -- Changing hp might kill the player causing mods to do who-knows-what to the
+ -- inventory, so do this before set_hp().
+ if replace_with_item then
+ if itemstack:is_empty() then
+ itemstack:add_item(replace_with_item)
+ else
+ local inv = user:get_inventory()
+ -- Check if inv is null, since non-players don't have one
+ if inv and inv:room_for_item("main", {name=replace_with_item}) then
+ inv:add_item("main", replace_with_item)
else
- local inv = user:get_inventory()
- -- Check if inv is null, since non-players don't have one
- if inv and inv:room_for_item("main", {name=replace_with_item}) then
- inv:add_item("main", replace_with_item)
- else
- local pos = user:get_pos()
- pos.y = math.floor(pos.y + 0.5)
- core.add_item(pos, replace_with_item)
- end
+ local pos = user:get_pos()
+ pos.y = math.floor(pos.y + 0.5)
+ core.add_item(pos, replace_with_item)
end
end
end
- return itemstack
+ user:set_wielded_item(itemstack)
+
+ user:set_hp(user:get_hp() + hp_change)
+
+ return nil -- don't overwrite wield item a second time
end
function core.item_eat(hp_change, replace_with_item)
@@ -537,11 +573,11 @@ function core.handle_node_drops(pos, drops, digger)
for _, dropped_item in pairs(drops) do
local left = give_item(dropped_item)
if not left:is_empty() then
- local p = {
- x = pos.x + math.random()/2-0.25,
- y = pos.y + math.random()/2-0.25,
- z = pos.z + math.random()/2-0.25,
- }
+ local p = vector.offset(pos,
+ math.random()/2-0.25,
+ math.random()/2-0.25,
+ math.random()/2-0.25
+ )
core.add_item(p, left)
end
end
@@ -578,7 +614,7 @@ function core.node_dig(pos, node, digger)
if wielded then
local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities()
- local dp = core.get_dig_params(def and def.groups, tp)
+ local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear())
if wdef and wdef.after_use then
wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else
@@ -600,7 +636,7 @@ function core.node_dig(pos, node, digger)
if def and def.preserve_metadata then
local oldmeta = core.get_meta(pos):to_table().fields
-- Copy pos and node because the callback can modify them.
- local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
+ local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@@ -632,7 +668,7 @@ function core.node_dig(pos, node, digger)
-- Run callback
if def and def.after_dig_node then
-- Copy pos and node because callback can modify them
- local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
+ local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
end
@@ -645,7 +681,7 @@ function core.node_dig(pos, node, digger)
end
-- Copy pos and node because callback can modify them
- local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
+ local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
callback(pos_copy, node_copy, digger)
end
@@ -688,7 +724,7 @@ core.nodedef_default = {
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
usable = false,
liquids_pointable = false,
@@ -747,7 +783,7 @@ core.craftitemdef_default = {
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
@@ -766,7 +802,7 @@ core.tooldef_default = {
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = 1,
liquids_pointable = false,
tool_capabilities = nil,
@@ -785,7 +821,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
index b8c5e16a9..e86efc50c 100644
--- a/builtin/game/misc.lua
+++ b/builtin/game/misc.lua
@@ -1,9 +1,21 @@
-- Minetest: builtin/misc.lua
+local S = core.get_translator("__builtin")
+
--
-- Misc. API functions
--
+-- @spec core.kick_player(String, String) :: Boolean
+function core.kick_player(player_name, reason)
+ if type(reason) == "string" then
+ reason = "Kicked: " .. reason
+ else
+ reason = "Kicked."
+ end
+ return core.disconnect_player(player_name, reason)
+end
+
function core.check_player_privs(name, ...)
if core.is_player(name) then
name = name:get_player_name()
@@ -42,15 +54,15 @@ end
function core.send_join_message(player_name)
if not core.is_singleplayer() then
- core.chat_send_all("*** " .. player_name .. " joined the game.")
+ core.chat_send_all("*** " .. S("@1 joined the game.", player_name))
end
end
function core.send_leave_message(player_name, timed_out)
- local announcement = "*** " .. player_name .. " left the game."
+ local announcement = "*** " .. S("@1 left the game.", player_name)
if timed_out then
- announcement = announcement .. " (timed out)"
+ announcement = "*** " .. S("@1 left the game (timed out).", player_name)
end
core.chat_send_all(announcement)
end
@@ -117,13 +129,12 @@ end
function core.get_position_from_hash(hash)
- local pos = {}
- pos.x = (hash % 65536) - 32768
+ local x = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
- pos.y = (hash % 65536) - 32768
+ local y = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
- pos.z = (hash % 65536) - 32768
- return pos
+ local z = (hash % 65536) - 32768
+ return vector.new(x, y, z)
end
@@ -213,7 +224,7 @@ function core.is_area_protected(minp, maxp, player_name, interval)
local y = math.floor(yf + 0.5)
for xf = minp.x, maxp.x, d.x do
local x = math.floor(xf + 0.5)
- local pos = {x = x, y = y, z = z}
+ local pos = vector.new(x, y, z)
if core.is_protected(pos, player_name) then
return pos
end
@@ -239,7 +250,7 @@ end
-- HTTP callback interface
-function core.http_add_fetch(httpenv)
+core.set_http_api_lua(function(httpenv)
httpenv.fetch = function(req, callback)
local handle = httpenv.fetch_async(req)
@@ -255,7 +266,8 @@ function core.http_add_fetch(httpenv)
end
return httpenv
-end
+end)
+core.set_http_api_lua = nil
function core.close_formspec(player_name, formname)
@@ -268,24 +280,44 @@ function core.cancel_shutdown_requests()
end
--- Callback handling for dynamic_add_media
+-- Used for callback handling with dynamic_add_media
+core.dynamic_media_callbacks = {}
+
-local dynamic_add_media_raw = core.dynamic_add_media_raw
-core.dynamic_add_media_raw = nil
-function core.dynamic_add_media(filepath, callback)
- local ret = dynamic_add_media_raw(filepath)
- if ret == false then
- return ret
+-- PNG encoder safety wrapper
+
+local o_encode_png = core.encode_png
+function core.encode_png(width, height, data, compression)
+ if type(width) ~= "number" then
+ error("Incorrect type for 'width', expected number, got " .. type(width))
end
- if callback == nil then
- core.log("deprecated", "Calling minetest.dynamic_add_media without "..
- "a callback is deprecated and will stop working in future versions.")
- else
- -- At the moment async loading is not actually implemented, so we
- -- immediately call the callback ourselves
- for _, name in ipairs(ret) do
- callback(name)
+ if type(height) ~= "number" then
+ error("Incorrect type for 'height', expected number, got " .. type(height))
+ end
+
+ local expected_byte_count = width * height * 4
+
+ if type(data) ~= "table" and type(data) ~= "string" then
+ error("Incorrect type for 'data', expected table or string, got " .. type(data))
+ end
+
+ local data_length = type(data) == "table" and #data * 4 or string.len(data)
+
+ if data_length ~= expected_byte_count then
+ error(string.format(
+ "Incorrect length of 'data', width and height imply %d bytes but %d were provided",
+ expected_byte_count,
+ data_length
+ ))
+ end
+
+ if type(data) == "table" then
+ local dataBuf = {}
+ for i = 1, #data do
+ dataBuf[i] = core.colorspec_to_bytes(data[i])
end
+ data = table.concat(dataBuf)
end
- return true
+
+ return o_encode_png(width, height, data, compression or 6)
end
diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua
index c7417d2f4..2ff4c093c 100644
--- a/builtin/game/privileges.lua
+++ b/builtin/game/privileges.lua
@@ -1,5 +1,7 @@
-- Minetest: builtin/privileges.lua
+local S = core.get_translator("__builtin")
+
--
-- Privileges
--
@@ -15,7 +17,7 @@ function core.register_privilege(name, param)
def.give_to_admin = def.give_to_singleplayer
end
if def.description == nil then
- def.description = "(no description)"
+ def.description = S("(no description)")
end
end
local def
@@ -28,71 +30,76 @@ function core.register_privilege(name, param)
core.registered_privileges[name] = def
end
-core.register_privilege("interact", "Can interact with things and modify the world")
-core.register_privilege("shout", "Can speak in chat")
-core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
-core.register_privilege("privs", "Can modify privileges")
+core.register_privilege("interact", S("Can interact with things and modify the world"))
+core.register_privilege("shout", S("Can speak in chat"))
+
+local basic_privs =
+ core.string_to_privs((core.settings:get("basic_privs") or "shout,interact"))
+local basic_privs_desc = S("Can modify basic privileges (@1)",
+ core.privs_to_string(basic_privs, ', '))
+core.register_privilege("basic_privs", basic_privs_desc)
+
+core.register_privilege("privs", S("Can modify privileges"))
core.register_privilege("teleport", {
- description = "Can teleport self",
+ description = S("Can teleport self"),
give_to_singleplayer = false,
})
core.register_privilege("bring", {
- description = "Can teleport other players",
+ description = S("Can teleport other players"),
give_to_singleplayer = false,
})
core.register_privilege("settime", {
- description = "Can set the time of day using /time",
+ description = S("Can set the time of day using /time"),
give_to_singleplayer = false,
})
core.register_privilege("server", {
- description = "Can do server maintenance stuff",
+ description = S("Can do server maintenance stuff"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("protection_bypass", {
- description = "Can bypass node protection in the world",
+ description = S("Can bypass node protection in the world"),
give_to_singleplayer = false,
})
core.register_privilege("ban", {
- description = "Can ban and unban players",
+ description = S("Can ban and unban players"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("kick", {
- description = "Can kick players",
+ description = S("Can kick players"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("give", {
- description = "Can use /give and /giveme",
+ description = S("Can use /give and /giveme"),
give_to_singleplayer = false,
})
core.register_privilege("password", {
- description = "Can use /setpassword and /clearpassword",
+ description = S("Can use /setpassword and /clearpassword"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("fly", {
- description = "Can use fly mode",
+ description = S("Can use fly mode"),
give_to_singleplayer = false,
})
core.register_privilege("fast", {
- description = "Can use fast mode",
+ description = S("Can use fast mode"),
give_to_singleplayer = false,
})
core.register_privilege("noclip", {
- description = "Can fly through solid nodes using noclip mode",
+ description = S("Can fly through solid nodes using noclip mode"),
give_to_singleplayer = false,
})
core.register_privilege("rollback", {
- description = "Can use the rollback functionality",
+ description = S("Can use the rollback functionality"),
give_to_singleplayer = false,
})
core.register_privilege("debug", {
- description = "Allows enabling various debug options that may affect gameplay",
+ description = S("Can enable wireframe"),
give_to_singleplayer = false,
- give_to_admin = true,
})
core.register_can_bypass_userlimit(function(name, ip)
diff --git a/builtin/game/register.lua b/builtin/game/register.lua
index 1cff85813..56e40c75c 100644
--- a/builtin/game/register.lua
+++ b/builtin/game/register.lua
@@ -1,5 +1,7 @@
-- Minetest: builtin/misc_register.lua
+local S = core.get_translator("__builtin")
+
--
-- Make raw registration functions inaccessible to anyone except this file
--
@@ -326,7 +328,7 @@ end
core.register_item(":unknown", {
type = "none",
- description = "Unknown Item",
+ description = S("Unknown Item"),
inventory_image = "unknown_item.png",
on_place = core.item_place,
on_secondary_use = core.item_secondary_use,
@@ -336,7 +338,7 @@ core.register_item(":unknown", {
})
core.register_node(":air", {
- description = "Air",
+ description = S("Air"),
inventory_image = "air.png",
wield_image = "air.png",
drawtype = "airlike",
@@ -353,7 +355,7 @@ core.register_node(":air", {
})
core.register_node(":ignore", {
- description = "Ignore",
+ description = S("Ignore"),
inventory_image = "ignore.png",
wield_image = "ignore.png",
drawtype = "airlike",
@@ -366,11 +368,12 @@ core.register_node(":ignore", {
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1},
+ node_placement_prediction = "",
on_place = function(itemstack, placer, pointed_thing)
core.chat_send_player(
placer:get_player_name(),
core.colorize("#FF0000",
- "You can't place 'ignore' nodes!"))
+ S("You can't place 'ignore' nodes!")))
return ""
end,
})
@@ -607,6 +610,7 @@ core.registered_on_modchannel_message, core.register_on_modchannel_message = mak
core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration()
+core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration()
--
-- Compatibility for on_mapgen_init()
diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua
index 724761414..64436bf1a 100644
--- a/builtin/game/voxelarea.lua
+++ b/builtin/game/voxelarea.lua
@@ -1,6 +1,6 @@
VoxelArea = {
- MinEdge = {x=1, y=1, z=1},
- MaxEdge = {x=0, y=0, z=0},
+ MinEdge = vector.new(1, 1, 1),
+ MaxEdge = vector.new(0, 0, 0),
ystride = 0,
zstride = 0,
}
@@ -19,11 +19,11 @@ end
function VoxelArea:getExtent()
local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
- return {
- x = MaxEdge.x - MinEdge.x + 1,
- y = MaxEdge.y - MinEdge.y + 1,
- z = MaxEdge.z - MinEdge.z + 1,
- }
+ return vector.new(
+ MaxEdge.x - MinEdge.x + 1,
+ MaxEdge.y - MinEdge.y + 1,
+ MaxEdge.z - MinEdge.z + 1
+ )
end
function VoxelArea:getVolume()