diff --git a/.github/workflows/cpp_lint.yml b/.github/workflows/cpp_lint.yml index 1f97d105a..2bd884c7a 100644 --- a/.github/workflows/cpp_lint.yml +++ b/.github/workflows/cpp_lint.yml @@ -24,20 +24,21 @@ on: - '.github/workflows/**.yml' jobs: - clang_format: - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: Install clang-format - run: | - sudo apt-get install clang-format-9 -qyy - - name: Run clang-format - run: | - source ./util/ci/lint.sh - perform_lint - env: - CLANG_FORMAT: clang-format-9 +# clang_format: +# runs-on: ubuntu-18.04 +# steps: +# - uses: actions/checkout@v2 +# - name: Install clang-format +# run: | +# sudo apt-get install clang-format-9 -qyy +# +# - name: Run clang-format +# run: | +# source ./util/ci/clang-format.sh +# check_format +# env: +# CLANG_FORMAT: clang-format-9 clang_tidy: runs-on: ubuntu-18.04 diff --git a/.gitignore b/.gitignore index 1cfca5fc2..3949bb44c 100644 --- a/.gitignore +++ b/.gitignore @@ -89,8 +89,7 @@ src/test_config.h src/cmake_config.h src/cmake_config_githash.h src/unittest/test_world/world.mt -src/lua/build/ -locale/ +/locale/ .directory *.cbp *.layout diff --git a/README.md b/README.md index cdeb0b417..235c7374d 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Some can be changed in the key config dialog in the settings tab. | P | Enable/disable pitch move mode | | J | Enable/disable fast mode (needs fast privilege) | | H | Enable/disable noclip mode (needs noclip privilege) | -| E | Move fast in fast mode | +| E | Aux1 (Move fast in fast mode. Games may add special features) | | C | Cycle through camera modes | | V | Cycle through minimap modes | | Shift + V | Change minimap orientation | diff --git a/build/android/icons/aux1_btn.svg b/build/android/icons/aux1_btn.svg new file mode 100644 index 000000000..e0ee97c0c --- /dev/null +++ b/build/android/icons/aux1_btn.svg @@ -0,0 +1,143 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + Aux1 + + + diff --git a/build/android/icons/aux_btn.svg b/build/android/icons/aux_btn.svg deleted file mode 100644 index 6bbefff67..000000000 --- a/build/android/icons/aux_btn.svg +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - AUX - - diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua index 0e8d4dd03..a563a6627 100644 --- a/builtin/client/chatcommands.lua +++ b/builtin/client/chatcommands.lua @@ -1,6 +1,5 @@ -- Minetest: builtin/client/chatcommands.lua - core.register_on_sending_chat_message(function(message) if message:sub(1,2) == ".." then return false @@ -8,7 +7,7 @@ core.register_on_sending_chat_message(function(message) local first_char = message:sub(1,1) if first_char == "/" or first_char == "." then - core.display_chat_message(core.gettext("issued command: ") .. message) + core.display_chat_message(core.gettext("Issued command: ") .. message) end if first_char ~= "." then @@ -19,7 +18,7 @@ core.register_on_sending_chat_message(function(message) param = param or "" if not cmd then - core.display_chat_message(core.gettext("-!- Empty command")) + core.display_chat_message("-!- " .. core.gettext("Empty command.")) return true end @@ -36,7 +35,7 @@ core.register_on_sending_chat_message(function(message) core.display_chat_message(result) end else - core.display_chat_message(core.gettext("-!- Invalid command: ") .. cmd) + core.display_chat_message("-!- " .. core.gettext("Invalid command: ") .. cmd) end return true @@ -66,7 +65,7 @@ core.register_chatcommand("clear_chat_queue", { description = core.gettext("Clear the out chat queue"), func = function(param) core.clear_out_chat_queue() - return true, core.gettext("The out chat queue is now empty") + return true, core.gettext("The out chat queue is now empty.") end, }) diff --git a/builtin/client/death_formspec.lua b/builtin/client/death_formspec.lua index 387b4ba6c..04e8e69ea 100644 --- a/builtin/client/death_formspec.lua +++ b/builtin/client/death_formspec.lua @@ -2,7 +2,7 @@ -- handled by the engine. core.register_on_death(function() - core.display_chat_message(fgettext("You died")) + core.display_chat_message(core.gettext("You died.")) local formspec = "size[8,5]bgcolor[#320000b4;true]" .. "background9[0,0;0,0;bg_common.png;true;40]" .. "style[you_died;font_size=+4;content_offset=0]" .. diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua index 52edda659..c945e7bdb 100644 --- a/builtin/common/chatcommands.lua +++ b/builtin/common/chatcommands.lua @@ -1,5 +1,9 @@ -- Minetest: builtin/common/chatcommands.lua +-- For server-side translations (if INIT == "game") +-- Otherwise, use core.gettext +local S = core.get_translator("__builtin") + core.registered_chatcommands = {} function core.register_chatcommand(cmd, def) @@ -29,25 +33,12 @@ function core.override_chatcommand(name, redefinition) core.registered_chatcommands[name] = chatcommand end -local cmd_marker = "/" - -local function gettext(...) - return ... -end - -local function gettext_replace(text, replace) - return text:gsub("$1", replace) -end - - -if INIT == "client" then - cmd_marker = "." - gettext = core.gettext - gettext_replace = fgettext_ne -end - local function do_help_cmd(name, param) local function format_help_line(cmd, def) + local cmd_marker = "/" + if INIT == "client" then + cmd_marker = "." + end local msg = core.colorize("#00ffff", cmd_marker .. cmd) if def.params and def.params ~= "" then msg = msg .. " " .. def.params @@ -65,9 +56,21 @@ local function do_help_cmd(name, param) end end table.sort(cmds) - return true, gettext("Available commands: ") .. table.concat(cmds, " ") .. "\n" - .. gettext_replace("Use '$1help ' to get more information," - .. " or '$1help all' to list everything.", cmd_marker) + local msg + if INIT == "game" then + msg = S("Available commands: @1", + table.concat(cmds, " ")) .. "\n" + .. S("Use '/help ' to get more " + .. "information, or '/help all' to list " + .. "everything.") + else + msg = core.gettext("Available commands: ") + .. table.concat(cmds, " ") .. "\n" + .. core.gettext("Use '.help ' to get more " + .. "information, or '.help all' to list " + .. "everything.") + end + return true, msg elseif param == "all" then local cmds = {} for cmd, def in pairs(core.registered_chatcommands) do @@ -76,19 +79,31 @@ local function do_help_cmd(name, param) end end table.sort(cmds) - return true, gettext("Available commands:").."\n"..table.concat(cmds, "\n") + local msg + if INIT == "game" then + msg = S("Available commands:") + else + msg = core.gettext("Available commands:") + end + return true, msg.."\n"..table.concat(cmds, "\n") elseif INIT == "game" and param == "privs" then local privs = {} for priv, def in pairs(core.registered_privileges) do privs[#privs + 1] = priv .. ": " .. def.description end table.sort(privs) - return true, "Available privileges:\n"..table.concat(privs, "\n") + return true, S("Available privileges:").."\n"..table.concat(privs, "\n") else local cmd = param local def = core.registered_chatcommands[cmd] if not def then - return false, gettext("Command not available: ")..cmd + local msg + if INIT == "game" then + msg = S("Command not available: @1", cmd) + else + msg = core.gettext("Command not available: ") .. cmd + end + return false, msg else return true, format_help_line(cmd, def) end @@ -97,16 +112,16 @@ end if INIT == "client" then core.register_chatcommand("help", { - params = gettext("[all | ]"), - description = gettext("Get help for commands"), + params = core.gettext("[all | ]"), + description = core.gettext("Get help for commands"), func = function(param) return do_help_cmd(nil, param) end, }) else core.register_chatcommand("help", { - params = "[all | privs | ]", - description = "Get help for commands or list privileges", + params = S("[all | privs | ]"), + description = S("Get help for commands or list privileges"), func = do_help_cmd, }) end diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua index 0994f2793..d83f32f82 100644 --- a/builtin/common/information_formspecs.lua +++ b/builtin/common/information_formspecs.lua @@ -22,7 +22,8 @@ local LIST_FORMSPEC_DESCRIPTION = [[ button_exit[5,7;3,1;quit;%s] ]] -local formspec_escape = core.formspec_escape +local F = core.formspec_escape +local S = core.get_translator("__builtin") local check_player_privs = core.check_player_privs @@ -53,16 +54,17 @@ core.after(0, load_mod_command_tree) local function build_chatcommands_formspec(name, sel, copy) local rows = {} - rows[1] = "#FFF,0,Command,Parameters" + rows[1] = "#FFF,0,"..F(S("Command"))..","..F(S("Parameters")) - local description = "For more information, click on any entry in the list.\n" .. - "Double-click to copy the entry to the chat history." + local description = S("For more information, click on " + .. "any entry in the list.").. "\n" .. + S("Double-click to copy the entry to the chat history.") for i, data in ipairs(mod_cmds) do for _, cmds in ipairs(data[2]) do local has_priv = check_player_privs(name, cmds[2].privs) if has_priv then - rows[#rows + 1] = COLOR_BLUE .. ",0," .. formspec_escape(data[1]) .. "," + rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. "," break end end @@ -72,11 +74,11 @@ local function build_chatcommands_formspec(name, sel, copy) rows[#rows + 1] = ("%s,1,%s,%s"):format( -- has_priv and COLOR_GREEN or COLOR_GRAY, COLOR_GREEN, - cmds[1], formspec_escape(cmds[2].params)) + cmds[1], F(cmds[2].params)) if sel == #rows then description = cmds[2].description if copy then - core.chat_send_player(name, ("Command: %s %s"):format( + core.chat_send_player(name, S("Command: @1 @2", core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)) end end @@ -85,9 +87,9 @@ local function build_chatcommands_formspec(name, sel, copy) end return LIST_FORMSPEC_DESCRIPTION:format( - "Available commands: (see also: /help )", + F(S("Available commands: (see also: /help )")), table.concat(rows, ","), sel or 0, - description, "Close" + F(description), F(S("Close")) ) end @@ -102,19 +104,19 @@ local function build_privs_formspec(name) table.sort(privs, function(a, b) return a[1] < b[1] end) local rows = {} - rows[1] = "#FFF,0,Privilege,Description" + rows[1] = "#FFF,0,"..F(S("Privilege"))..","..F(S("Description")) local player_privs = core.get_player_privs(name) for i, data in ipairs(privs) do rows[#rows + 1] = ("%s,0,%s,%s"):format( player_privs[data[1]] and COLOR_GREEN or COLOR_GRAY, - data[1], formspec_escape(data[2].description)) + data[1], F(data[2].description)) end return LIST_FORMSPEC:format( - "Available privileges:", + F(S("Available privileges:")), table.concat(rows, ","), - "Close" + F(S("Close")) ) end diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index befdd47f9..340122bf2 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) @@ -52,7 +54,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,7 +67,7 @@ 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) @@ -73,7 +75,7 @@ core.register_on_chat_message(function(name, message) core.set_last_run_mod(cmd_def.mod_origin) local success, result = cmd_def.func(name, param) 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) @@ -85,9 +87,10 @@ core.register_on_chat_message(function(name, message) core.chat_send_player(name, result) 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 +110,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 @@ -132,9 +136,9 @@ core.register_chatcommand_alias = register_chatcommand_alias -- Chat commands -- core.register_chatcommand("me", { - params = "", - description = "Show chat action (e.g., '/me orders a pizza' displays" - .. " ' orders a pizza')", + params = S(""), + description = S("Show chat action (e.g., '/me orders a pizza' " + .. "displays ' orders a pizza')"), privs = {shout=true}, func = function(name, param) core.chat_send_all("* " .. name .. " " .. param) @@ -143,43 +147,44 @@ 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, }) core.register_chatcommand("privs", { - params = "[]", - description = "Show privileges of yourself or another player", + params = S("[]"), + 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, S("Privileges of @1: @2", name, + core.privs_to_string( + core.get_player_privs(name), ", ")) end, }) core.register_chatcommand("haspriv", { - params = "", - description = "Return list of all online players with privilege.", + params = S(""), + 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 = {} @@ -189,19 +194,20 @@ 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, ", ") + return true, S("Players online with the \"@1\" privilege: @2", + param, + table.concat(players_with_priv, ", ")) end }) local function handle_grant_command(caller, grantname, grantprivstr) local caller_privs = core.get_player_privs(caller) if not (caller_privs.privs or caller_privs.basic_privs) then - return false, "Your privileges are insufficient." + 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 @@ -213,10 +219,10 @@ 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.") 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 @@ -230,33 +236,33 @@ local function handle_grant_command(caller, grantname, grantprivstr) 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, S("Privileges of @1: @2", grantname, + core.privs_to_string( + core.get_player_privs(grantname), ' ')) end core.register_chatcommand("grant", { - params = " ( | all)", - description = "Give privileges to player", + params = S(" ( | 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 = " | all", - description = "Grant privileges to yourself", + params = S(" | 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, @@ -265,11 +271,11 @@ 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) @@ -278,7 +284,7 @@ local function handle_revoke_command(caller, revokename, revokeprivstr) 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." + return false, S("Your privileges are insufficient.") end end @@ -301,43 +307,43 @@ local function handle_revoke_command(caller, revokename, revokeprivstr) ..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, S("Privileges of @1: @2", revokename, + core.privs_to_string( + core.get_player_privs(revokename), ' ')) end core.register_chatcommand("revoke", { - params = " ( | all)", - description = "Remove privileges from player", + params = S(" ( | 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 = " | all", - description = "Revoke privileges from yourself", + params = S(" | 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 = " ", - description = "Set player's password", + params = S(" "), + description = S("Set player's password"), privs = {password=true}, func = function(name, param) local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$") @@ -347,83 +353,83 @@ 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 = "", - description = "Set empty password for a player", + params = S(""), + 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 = "", - description = "Remove a player's data", + params = S(""), + 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, }) @@ -454,46 +460,46 @@ local function teleport_to_pos(name, p) 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!" + return false, S("Cannot teleport out of map bounds!") end local teleportee = core.get_player_by_name(name) if not teleportee then - return false, "Cannot get player with name " .. name + return false, S("Cannot get player with name @1.", name) end if teleportee:get_attach() then - return false, "Cannot teleport, " .. name .. - " is attached to an object!" + return false, S("Cannot teleport, @1 " .. + "is attached to an object!", name) end teleportee:set_pos(p) - return true, "Teleporting " .. name .. " to " .. core.pos_to_string(p, 1) + return true, S("Teleporting @1 to @2.", name, core.pos_to_string(p, 1)) end -- Teleports player next to player if possible local function teleport_to_player(name, target_name) if name == target_name then - return false, "One does not teleport to oneself." + return false, S("One does not teleport to oneself.") end local teleportee = core.get_player_by_name(name) if not teleportee then - return false, "Cannot get teleportee with name " .. name + return false, S("Cannot get teleportee with name @1.", name) end if teleportee:get_attach() then - return false, "Cannot teleport, " .. name .. - " is attached to an object!" + 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, "Cannot get target player with name " .. target_name + 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, "Teleporting " .. name .. " to " .. target_name .. " at " .. - core.pos_to_string(p, 1) + return true, S("Teleporting @1 to @2 at @3.", name, target_name, + core.pos_to_string(p, 1)) end core.register_chatcommand("teleport", { - params = ",, | | ,, | ", - description = "Teleport to position or player", + params = S(",, | | ,, | "), + description = S("Teleport to position or player"), privs = {teleport=true}, func = function(name, param) local p = {} @@ -509,8 +515,8 @@ core.register_chatcommand("teleport", { end local has_bring_priv = core.check_player_privs(name, {bring=true}) - local missing_bring_msg = "You don't have permission to teleport " .. - "other players (missing bring privilege)" + local missing_bring_msg = S("You don't have permission to teleport " .. + "other players (missing privilege: @1).", "bring") local teleportee_name teleportee_name, p.x, p.y, p.z = param:match( @@ -537,8 +543,8 @@ core.register_chatcommand("teleport", { register_chatcommand_alias("tp", "teleport") core.register_chatcommand("set", { - params = "([-n] ) | ", - description = "Set or read server configuration setting", + params = S("([-n] ) | "), + description = S("Set or read server configuration setting"), privs = {server=true}, func = function(name, param) local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") @@ -551,23 +557,24 @@ 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 ' to create a new setting." + return false, S("Failed. Use '/set -n ' " + .. "to create a new setting.") end core.settings:set(setname, setvalue) core.settings:write() - 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 = "" + setvalue = S("") 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, }) @@ -580,26 +587,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 []) | ( )", - description = "Load (or, if nonexistent, generate) map blocks " - .. "contained in area pos1 to pos2 ( and must be in parentheses)", + params = S("(here []) | ( )"), + description = S("Load (or, if nonexistent, generate) map blocks contained in " + .. "area pos1 to pos2 ( and must be in parentheses)"), privs = {server=true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) @@ -617,15 +625,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 []) | ( )", - description = "Delete map blocks contained in area pos1 to pos2 " - .. "( and must be in parentheses)", + params = S("(here []) | ( )"), + description = S("Delete map blocks contained in area pos1 to pos2 " + .. "( and must be in parentheses)"), privs = {server=true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) @@ -634,18 +642,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 []) | ( )", - description = "Resets lighting in the area between pos1 and pos2 " - .. "( and must be in parentheses)", + params = S("(here []) | ( )"), + description = S("Resets lighting in the area between pos1 and pos2 " + .. "( and must be in parentheses)"), privs = {server = true}, func = function(name, param) local p1, p2 = parse_range_str(name, param) @@ -654,17 +664,18 @@ 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 = {server = true}, func = function(name, param) return true, table.concat(core.get_modnames(), ", ") @@ -688,117 +699,136 @@ local function handle_give_command(cmd, giver, receiver, stackstring) end 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() - if giver == receiver then - local msg = "%q %sadded to inventory." - return true, msg:format(stackstring, partiality) + 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 - 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) + msg = S("@1 added to inventory.", stackstring) + end + if giver == receiver then + return true, msg + else + 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 = " [ []]", - description = "Give item to player", + params = S(" [ []]"), + 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 = " [ []]", - description = "Give item to yourself", + params = S(" [ []]"), + 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 = " [,,]", - description = "Spawn entity at given (or your) position", + params = S(" [,,]"), + 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, }) @@ -814,14 +844,15 @@ core.register_on_punchnode(function(pos, node, puncher) end) core.register_chatcommand("rollback_check", { - params = "[] [] []", - description = "Check who last touched a node or a node near it" - .. " within the time specified by . Default: range = 0," - .. " seconds = 86400 = 24h, limit = 5. Set to inf for no time limit", + params = S("[] [] []"), + description = S("Check who last touched a node or a node near it " + .. "within the time specified by . " + .. "Default: range = 0, seconds = 86400 = 24h, limit = 5. " + .. "Set 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*)") @@ -829,30 +860,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, @@ -861,110 +892,123 @@ 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 = "( []) | (: [])", - description = "Revert actions of a player. Default for is 60. Set to inf for no time limit", + params = S("( []) | (: [])"), + description = S("Revert actions of a player. " + .. "Default for is 60. " + .. "Set 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) or -1 if new_time ~= new_time or new_time < 0 or new_time > 24000 then - return false, "Invalid time (must be between 0 and 24000)." + return false, S("Invalid time (must be between 0 and 24000).") end 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, }) register_chatcommand_alias("settime", "time") 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 }) core.register_chatcommand("shutdown", { - params = "[ | -1] [reconnect] []", - description = "Shutdown server (-1 cancels a delayed shutdown)", + params = S("[ | -1] [reconnect] []"), + description = S("Shutdown server (-1 cancels a delayed shutdown)"), privs = {server=true}, func = function(name, param) local delay, reconnect, message @@ -977,7 +1021,7 @@ core.register_chatcommand("shutdown", { 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) return true @@ -985,65 +1029,65 @@ core.register_chatcommand("shutdown", { }) core.register_chatcommand("ban", { - params = "[]", - description = "Ban the IP of a player or show the ban list", + params = S("[]"), + 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 = " | ", - description = "Remove IP ban belonging to a player/IP", + params = S(" | "), + 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 = " []", - description = "Kick a player", + params = S(" []"), + 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 = {} @@ -1052,46 +1096,45 @@ 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 (" .. options.mode .. " mode).") - core.chat_send_all("Clearing all objects. This may take a long time." - .. " You may experience a timeout.") + core.chat_send_all(S("Clearing all objects. This may take a long time." + .. " You may experience a timeout.")) 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 = " ", - description = "Send a private message to a player", + params = S(" "), + description = S("Send a private 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", "PM from " .. name .. " to " .. sendto .. ": " .. message) core.chat_send_player(sendto, core.colorize("green", - "PM from " .. name .. ": " .. message)) - return true, "Message sent." + S("PM from @1: @2", name, message))) + return true, S("Message sent.") end, }) register_chatcommand_alias("m", "msg") register_chatcommand_alias("pm", "msg") core.register_chatcommand("last-login", { - params = "[]", - description = "Get the last login time of a player or yourself", + params = S("[]"), + description = S("Get the last login time of a player or yourself"), func = function(name, param) if param == "" then param = name @@ -1099,25 +1142,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 = "[]", - description = "Clear the inventory of yourself or another player", + params = S("[]"), + 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 @@ -1127,25 +1172,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 killer ~= victim then @@ -1153,12 +1198,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 = "[]", - description = "Kill player or yourself", + params = S("[]"), + description = S("Kill player or yourself"), privs = {server=true}, func = function(name, param) return handle_kill_command(name, param == "" and name or param) @@ -1184,7 +1229,7 @@ core.register_chatcommand("spawn", { }) core.register_chatcommand("setspawn", { - description = "Sets the spawn point to your current position", + description = S("Sets the spawn point to your current position"), privs = {server = true}, func = function(name) local player = core.get_player_by_name(name) @@ -1205,6 +1250,7 @@ core.register_chatcommand("setspawn", { core.set_world_spawnpoint(pos) - return true, "The spawn point has been set to " .. core.pos_to_string(pos) + return true, S("The spawn point has been set to @1", + core.pos_to_string(pos)) end }) diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua index 5aa1544ea..7e3684a85 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 @@ -33,69 +35,69 @@ if core.settings:get_bool("creative_mode") then creative = true 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")) +core.register_privilege("basic_privs", S("Can modify 'shout' and 'interact' privileges")) +core.register_privilege("privs", S("Can modify privileges")) core.register_privilege("teleport", { - description = "Can teleport self", + description = S("Can teleport self"), give_to_singleplayer = creative, }) 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 = creative, }) 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 = creative, }) core.register_privilege("fast", { - description = "Can use fast mode", + description = S("Can use fast mode"), give_to_singleplayer = creative, }) 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("Allows enabling various debug options that may affect gameplay"), give_to_singleplayer = false, give_to_admin = true, }) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 418dc06ec..0fa0e3015 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 -- @@ -329,7 +331,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, @@ -339,7 +341,7 @@ core.register_item(":unknown", { }) core.register_node(":air", { - description = "Air", + description = S("Air"), inventory_image = "blank.png", wield_image = "blank.png", drawtype = "airlike", @@ -357,7 +359,7 @@ core.register_node(":air", { }) core.register_node(":ignore", { - description = "Ignore", + description = S("Ignore"), inventory_image = "ignore.png", wield_image = "ignore.png", drawtype = "airlike", @@ -375,7 +377,7 @@ core.register_node(":ignore", { 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, }) diff --git a/builtin/locale/template.txt b/builtin/locale/template.txt new file mode 100644 index 000000000..d85c53b68 --- /dev/null +++ b/builtin/locale/template.txt @@ -0,0 +1,226 @@ +# textdomain: __builtin +Empty command.= +Invalid command: @1= +Invalid command usage.= +You don't have permission to run this command (missing privileges: @1).= +Unable to get position of player @1.= +Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)= += +Show chat action (e.g., '/me orders a pizza' displays ' orders a pizza')= +Show the name of the server owner= +The administrator of this server is @1.= +There's no administrator named in the config file.= +[]= +Show privileges of yourself or another player= +Player @1 does not exist.= +Privileges of @1: @2= += +Return list of all online players with privilege= +Invalid parameters (see /help haspriv).= +Unknown privilege!= +Players online with the "@1" privilege: @2= +Your privileges are insufficient.= +Unknown privilege: @1= +@1 granted you privileges: @2= + ( | all)= +Give privileges to player= +Invalid parameters (see /help grant).= + | all= +Grant privileges to yourself= +Invalid parameters (see /help grantme).= +@1 revoked privileges from you: @2= +Remove privileges from player= +Invalid parameters (see /help revoke).= +Revoke privileges from yourself= +Invalid parameters (see /help revokeme).= + = +Set player's password= +Name field required.= +Your password was cleared by @1.= +Password of player "@1" cleared.= +Your password was set by @1.= +Password of player "@1" set.= += +Set empty password for a player= +Reload authentication data= +Done.= +Failed.= +Remove a player's data= +Player "@1" removed.= +No such player "@1" to remove.= +Player "@1" is connected, cannot remove.= +Unhandled remove_player return code @1.= +Cannot teleport out of map bounds!= +Cannot get player with name @1.= +Cannot teleport, @1 is attached to an object!= +Teleporting @1 to @2.= +One does not teleport to oneself.= +Cannot get teleportee with name @1.= +Cannot get target player with name @1.= +Teleporting @1 to @2 at @3.= +,, | | ,, | = +Teleport to position or player= +You don't have permission to teleport other players (missing privilege: @1).= +([-n] ) | = +Set or read server configuration setting= +Failed. Use '/set -n ' to create a new setting.= +@1 @= @2= += +Invalid parameters (see /help set).= +Finished emerging @1 blocks in @2ms.= +emergeblocks update: @1/@2 blocks emerged (@3%)= +(here []) | ( )= +Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 ( and must be in parentheses)= +Started emerge of area ranging from @1 to @2.= +Delete map blocks contained in area pos1 to pos2 ( and must be in parentheses)= +Successfully cleared area ranging from @1 to @2.= +Failed to clear one or more blocks in area.= +Resets lighting in the area between pos1 and pos2 ( and must be in parentheses)= +Successfully reset light in the area ranging from @1 to @2.= +Failed to load one or more blocks in area.= +List mods installed on the server= +Cannot give an empty item.= +Cannot give an unknown item.= +Giving 'ignore' is not allowed.= +@1 is not a known player.= +@1 partially added to inventory.= +@1 could not be added to inventory.= +@1 added to inventory.= +@1 partially added to inventory of @2.= +@1 could not be added to inventory of @2.= +@1 added to inventory of @2.= + [ []]= +Give item to player= +Name and ItemString required.= + [ []]= +Give item to yourself= +ItemString required.= + [,,]= +Spawn entity at given (or your) position= +EntityName required.= +Unable to spawn entity, player is nil.= +Cannot spawn an unknown entity.= +Invalid parameters (@1).= +@1 spawned.= +@1 failed to spawn.= +Destroy item in hand= +Unable to pulverize, no player.= +Unable to pulverize, no item in hand.= +An item was pulverized.= +[] [] []= +Check who last touched a node or a node near it within the time specified by . Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set to inf for no time limit= +Rollback functions are disabled.= +That limit is too high!= +Checking @1 ...= +Nobody has touched the specified location in @1 seconds.= +@1 @2 @3 -> @4 @5 seconds ago.= +Punch a node (range@=@1, seconds@=@2, limit@=@3).= +( []) | (: [])= +Revert actions of a player. Default for is 60. Set to inf for no time limit= +Invalid parameters. See /help rollback and /help rollback_check.= +Reverting actions of player '@1' since @2 seconds.= +Reverting actions of @1 since @2 seconds.= +(log is too long to show)= +Reverting actions succeeded.= +Reverting actions FAILED.= +Show server status= +This command was disabled by a mod or game.= +[<0..23>:<0..59> | <0..24000>]= +Show or set time of day= +Current time is @1:@2.= +You don't have permission to run this command (missing privilege: @1).= +Invalid time.= +Time of day changed.= +Invalid hour (must be between 0 and 23 inclusive).= +Invalid minute (must be between 0 and 59 inclusive).= +Show day count since world creation= +Current day is @1.= +[ | -1] [reconnect] []= +Shutdown server (-1 cancels a delayed shutdown)= +Server shutting down (operator request).= +Ban the IP of a player or show the ban list= +The ban list is empty.= +Ban list: @1= +Player is not online.= +Failed to ban player.= +Banned @1.= + | = +Remove IP ban belonging to a player/IP= +Failed to unban player/IP.= +Unbanned @1.= + []= +Kick a player= +Failed to kick player @1.= +Kicked @1.= +[full | quick]= +Clear all objects in world= +Invalid usage, see /help clearobjects.= +Clearing all objects. This may take a long time. You may experience a timeout.= +Cleared all objects.= + = +Send a private message to a player= +Invalid usage, see /help msg.= +The player @1 is not online.= +PM from @1: @2= +Message sent.= +Get the last login time of a player or yourself= +@1's last login time was @2.= +@1's last login time is unknown.= +Clear the inventory of yourself or another player= +You don't have permission to clear another player's inventory (missing privilege: @1).= +@1 cleared your inventory.= +Cleared @1's inventory.= +Player must be online to clear inventory!= +Players can't be killed, damage has been disabled.= +Player @1 is not online.= +You are already dead.= +@1 is already dead.= +@1 has been killed.= +Kill player or yourself= +Available commands: @1= +Use '/help ' to get more information, or '/help all' to list everything.= +Available commands:= +Command not available: @1= +[all | privs | ]= +Get help for commands or list privileges= +Available privileges:= +Command= +Parameters= +For more information, click on any entry in the list.= +Double-click to copy the entry to the chat history.= +Command: @1 @2= +Available commands: (see also: /help )= +Close= +Privilege= +Description= +print [] | dump [] | save [ []] | reset= +Handle the profiler and profiling data= +Statistics written to action log.= +Statistics were reset.= +Usage: @1= +Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).= +(no description)= +Can interact with things and modify the world= +Can speak in chat= +Can modify 'shout' and 'interact' privileges= +Can modify privileges= +Can teleport self= +Can teleport other players= +Can set the time of day using /time= +Can do server maintenance stuff= +Can bypass node protection in the world= +Can ban and unban players= +Can kick players= +Can use /give and /giveme= +Can use /setpassword and /clearpassword= +Can use fly mode= +Can use fast mode= +Can fly through solid nodes using noclip mode= +Can use the rollback functionality= +Allows enabling various debug options that may affect gameplay= +Unknown Item= +Air= +Ignore= +You can't place 'ignore' nodes!= +Sets the spawn point to your current position= +The spawn point has been set to @1= diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index a6c83de7a..c89777c90 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -158,11 +158,26 @@ local function get_formspec(tabview, name, tabdata) return retval end +-------------------------------------------------------------------------------- +local function handle_doubleclick(pkg) + if pkg.type == "txp" then + if core.settings:get("texture_path") == pkg.path then + core.settings:set("texture_path", "") + else + core.settings:set("texture_path", pkg.path) + end + packages = nil + end +end + -------------------------------------------------------------------------------- local function handle_buttons(tabview, fields, tabname, tabdata) if fields["pkglist"] ~= nil then local event = core.explode_table_event(fields["pkglist"]) tabdata.selected_pkg = event.row + if event.type == "DCL" then + handle_doubleclick(packages:get_list()[tabdata.selected_pkg]) + end return true end diff --git a/builtin/profiler/init.lua b/builtin/profiler/init.lua index cefb4c6ad..e41f57cde 100644 --- a/builtin/profiler/init.lua +++ b/builtin/profiler/init.lua @@ -15,6 +15,8 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +local S = core.get_translator("__builtin") + local function get_bool_default(name, default) local val = core.settings:get_bool(name) if val == nil then @@ -40,9 +42,9 @@ function profiler.init_chatcommand() instrumentation.init_chatcommand() end - local param_usage = "print [filter] | dump [filter] | save [format [filter]] | reset" + local param_usage = S("print [] | dump [] | save [ []] | reset") core.register_chatcommand("profiler", { - description = "handle the profiler and profiling data", + description = S("Handle the profiler and profiling data"), params = param_usage, privs = { server=true }, func = function(name, param) @@ -51,21 +53,19 @@ function profiler.init_chatcommand() if command == "dump" then core.log("action", reporter.print(sampler.profile, arg0)) - return true, "Statistics written to action log" + return true, S("Statistics written to action log.") elseif command == "print" then return true, reporter.print(sampler.profile, arg0) elseif command == "save" then return reporter.save(sampler.profile, args[1] or "txt", args[2]) elseif command == "reset" then sampler.reset() - return true, "Statistics were reset" + return true, S("Statistics were reset.") end - return false, string.format( - "Usage: %s\n" .. - "Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).", - param_usage - ) + return false, + S("Usage: @1", param_usage) .. "\n" .. + S("Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).") end }) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 89727a39d..2ed783442 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -75,7 +75,7 @@ free_move (Flying) bool false # If enabled, makes move directions relative to the player's pitch when flying or swimming. pitch_move (Pitch move mode) bool false -# Fast movement (via the "special" key). +# Fast movement (via the "Aux1" key). # This requires the "fast" privilege on the server. fast_move (Fast movement) bool false @@ -99,14 +99,14 @@ invert_mouse (Invert mouse) bool false # Mouse sensitivity multiplier. mouse_sensitivity (Mouse sensitivity) float 0.2 -# If enabled, "special" key instead of "sneak" key is used for climbing down and +# If enabled, "Aux1" key instead of "Sneak" key is used for climbing down and # descending. -aux1_descends (Special key for climbing/descending) bool false +aux1_descends (Aux1 key for climbing/descending) bool false # Double-tapping the jump key toggles fly mode. doubletap_jump (Double tap jump for fly) bool false -# If disabled, "special" key is used to fly fast if both fly and fast mode are +# If disabled, "Aux1" key is used to fly fast if both fly and fast mode are # enabled. always_fly_fast (Always fly and fast) bool true @@ -135,9 +135,9 @@ touchscreen_threshold (Touch screen threshold) int 20 0 100 # If disabled, virtual joystick will center to first-touch's position. fixed_virtual_joystick (Fixed virtual joystick) bool false -# (Android) Use virtual joystick to trigger "aux" button. -# If enabled, virtual joystick will also tap "aux" button when out of main circle. -virtual_joystick_triggers_aux (Virtual joystick triggers aux button) bool false +# (Android) Use virtual joystick to trigger "Aux1" button. +# If enabled, virtual joystick will also tap "Aux1" button when out of main circle. +virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false # Enable joysticks enable_joysticks (Enable joysticks) bool false @@ -199,7 +199,7 @@ keymap_inventory (Inventory key) key KEY_KEY_I # Key for moving fast in fast mode. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 -keymap_special1 (Special key) key KEY_KEY_E +keymap_aux1 (Aux1 key) key KEY_KEY_E # Key for opening the chat window. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 diff --git a/client/shaders/3d_interlaced_merge/opengl_fragment.glsl b/client/shaders/3d_interlaced_merge/opengl_fragment.glsl index 7cba61b39..6d3ae5093 100644 --- a/client/shaders/3d_interlaced_merge/opengl_fragment.glsl +++ b/client/shaders/3d_interlaced_merge/opengl_fragment.glsl @@ -6,7 +6,7 @@ uniform sampler2D textureFlags; #define rightImage normalTexture #define maskImage textureFlags -varying mediump vec2 varTexCoord; +varying mediump vec4 varTexCoord; void main(void) { diff --git a/client/shaders/3d_interlaced_merge/opengl_vertex.glsl b/client/shaders/3d_interlaced_merge/opengl_vertex.glsl index 860049481..224b7d183 100644 --- a/client/shaders/3d_interlaced_merge/opengl_vertex.glsl +++ b/client/shaders/3d_interlaced_merge/opengl_vertex.glsl @@ -1,4 +1,4 @@ -varying mediump vec2 varTexCoord; +varying mediump vec4 varTexCoord; void main(void) { diff --git a/clientmods/preview/mod.conf b/clientmods/preview/mod.conf new file mode 100644 index 000000000..4e56ec293 --- /dev/null +++ b/clientmods/preview/mod.conf @@ -0,0 +1 @@ +name = preview diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index bc8a823a6..90066124e 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -166,6 +166,15 @@ void ClientMap::updateDrawList() v3s16 p_blocks_max; getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max); + // Read the vision range, unless unlimited range is enabled. +#if !defined(__ANDROID__) && !defined(__IOS__) + float range = m_control.range_all ? 1e7 : m_control.wanted_range; +#else + // On mobile, "unlimited" range is only four times the regular range to + // prevent large FPS drops. + float range = m_control.range_all ? m_control.wanted_range * 4 : m_control.wanted_range; +#endif + // Number of blocks currently loaded by the client u32 blocks_loaded = 0; // Number of blocks with mesh in rendering range @@ -183,6 +192,7 @@ void ClientMap::updateDrawList() occlusion_culling_enabled = false; } + // Uncomment to debug occluded blocks in the wireframe mode // TODO: Include this as a flag for an extended debugging setting //if (occlusion_culling_enabled && m_control.show_wireframe) @@ -219,33 +229,31 @@ void ClientMap::updateDrawList() continue; } -#if !defined(__ANDROID__) && !defined(__IOS__) - float range = 100000 * BS; -#else - float range = m_control.wanted_range * BS * 4; -#endif - if (!m_control.range_all) - range = m_control.wanted_range * BS; + v3s16 block_coord = block->getPos(); + v3s16 block_position = block->getPosRelative() + MAP_BLOCKSIZE / 2; - float d = 0.0; - if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, range, &d)) - continue; + // First, perform a simple distance check, with a padding of one extra block. + if (!m_control.range_all && + block_position.getDistanceFrom(cam_pos_nodes) > range + MAP_BLOCKSIZE) + continue; // Out of range, skip. + // Keep the block alive as long as it is in range. + block->resetUsageTimer(); blocks_in_range_with_mesh++; - /* - Occlusion culling - */ + // Frustum culling + float d = 0.0; + if (!isBlockInSight(block_coord, camera_position, + camera_direction, camera_fov, range * BS, &d)) + continue; + + // Occlusion culling if ((!m_control.range_all && d > m_control.wanted_range * BS) || (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes))) { blocks_occlusion_culled++; continue; } - // This block is in range. Reset usage timer. - block->resetUsageTimer(); - // Add to set block->refGrab(); m_drawlist.push_back({block, d}); @@ -293,8 +301,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) const u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); const v3f camera_position = m_camera_position; - const v3f camera_direction = m_camera_direction; - const f32 camera_fov = m_camera_fov; /* Get all blocks and draw all visible ones @@ -316,15 +322,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) for (auto &item : m_drawlist) { MapBlock *block = item.block; v3s16 block_pos = block->getPos(); - float d; - if (!isBlockInSight(block_pos, camera_position, - camera_direction, camera_fov, 100000 * BS, &d)) - continue; MapBlockMesh *mapBlockMesh = block->mesh; if (!mapBlockMesh) continue; + v3f block_pos_r = intToFloat(block->getPosRelative() + MAP_BLOCKSIZE / 2, BS); + float d = camera_position.getDistanceFrom(block_pos_r); + d = MYMAX(0,d - BLOCK_MAX_RADIUS); + // Mesh animation if (pass == scene::ESNRP_SOLID) { // Pretty random but this should work somewhat nicely diff --git a/src/client/game.cpp b/src/client/game.cpp index 4b767b0ec..179685fcc 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2526,7 +2526,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) input->isKeyDown(KeyType::LEFT), input->isKeyDown(KeyType::RIGHT), isKeyDown(KeyType::JUMP), - isKeyDown(KeyType::SPECIAL1), + isKeyDown(KeyType::AUX1), isKeyDown(KeyType::SNEAK), isKeyDown(KeyType::ZOOM), isKeyDown(KeyType::DIG), @@ -2543,7 +2543,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | - ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | + ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) | ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) | ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) | diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index ec618b00e..e3caa81e2 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -40,7 +40,7 @@ void KeyCache::populate() key[KeyType::LEFT] = getKeySetting("keymap_left"); key[KeyType::RIGHT] = getKeySetting("keymap_right"); key[KeyType::JUMP] = getKeySetting("keymap_jump"); - key[KeyType::SPECIAL1] = getKeySetting("keymap_special1"); + key[KeyType::AUX1] = getKeySetting("keymap_aux1"); key[KeyType::SNEAK] = getKeySetting("keymap_sneak"); key[KeyType::DIG] = getKeySetting("keymap_dig"); key[KeyType::PLACE] = getKeySetting("keymap_place"); @@ -238,7 +238,7 @@ void RandomInputHandler::step(float dtime) { static RandomInputHandlerSimData rnd_data[] = { { "keymap_jump", 0.0f, 40 }, - { "keymap_special1", 0.0f, 40 }, + { "keymap_aux1", 0.0f, 40 }, { "keymap_forward", 0.0f, 40 }, { "keymap_left", 0.0f, 40 }, { "keymap_dig", 0.0f, 30 }, diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index f4668b249..026671e00 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -79,7 +79,7 @@ JoystickLayout create_default_layout() // Accessible without any modifier pressed JLO_B_PB(KeyType::JUMP, bm | 1 << 0, 1 << 0); - JLO_B_PB(KeyType::SPECIAL1, bm | 1 << 1, 1 << 1); + JLO_B_PB(KeyType::AUX1, bm | 1 << 1, 1 << 1); // Accessible with start button not pressed, but four pressed // TODO find usage for button 0 @@ -126,11 +126,11 @@ JoystickLayout create_xbox_layout() // 4 Buttons JLO_B_PB(KeyType::JUMP, 1 << 0, 1 << 0); // A/green JLO_B_PB(KeyType::ESC, 1 << 1, 1 << 1); // B/red - JLO_B_PB(KeyType::SPECIAL1, 1 << 2, 1 << 2); // X/blue + JLO_B_PB(KeyType::AUX1, 1 << 2, 1 << 2); // X/blue JLO_B_PB(KeyType::INVENTORY, 1 << 3, 1 << 3); // Y/yellow // Analog Sticks - JLO_B_PB(KeyType::SPECIAL1, 1 << 11, 1 << 11); // left + JLO_B_PB(KeyType::AUX1, 1 << 11, 1 << 11); // left JLO_B_PB(KeyType::SNEAK, 1 << 12, 1 << 12); // right // Triggers diff --git a/src/client/keys.h b/src/client/keys.h index d4924a9d4..c8475b61c 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -32,7 +32,7 @@ public: LEFT, RIGHT, JUMP, - SPECIAL1, + AUX1, SNEAK, AUTOFORWARD, DIG, diff --git a/src/content/mods.cpp b/src/content/mods.cpp index fd1d405d2..1c7fd4b4a 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "porting.h" #include "convert_json.h" +#include "script/common/c_internal.h" bool parseDependsString(std::string &dep, std::unordered_set &symbols) { @@ -44,20 +45,24 @@ bool parseDependsString(std::string &dep, std::unordered_set &symbols) return !dep.empty(); } +static void log_mod_deprecation(const ModSpec &spec, const std::string &warning) +{ + auto handling_mode = get_deprecated_handling_mode(); + if (handling_mode != DeprecatedHandlingMode::Ignore) { + std::ostringstream os; + os << warning << " (" << spec.name << " at " << spec.path << ")" << std::endl; + + if (handling_mode == DeprecatedHandlingMode::Error) { + throw ModError(os.str()); + } else { + warningstream << os.str(); + } + } +} + void parseModContents(ModSpec &spec) { // NOTE: this function works in mutual recursion with getModsInPath - Settings info; - info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str()); - - if (info.exists("name")) - spec.name = info.get("name"); - - if (info.exists("author")) - spec.author = info.get("author"); - - if (info.exists("release")) - spec.release = info.getS32("release"); spec.depends.clear(); spec.optdepends.clear(); @@ -78,6 +83,20 @@ void parseModContents(ModSpec &spec) spec.modpack_content = getModsInPath(spec.path, true); } else { + Settings info; + info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str()); + + if (info.exists("name")) + spec.name = info.get("name"); + else + log_mod_deprecation(spec, "Mods not having a mod.conf file with the name is deprecated."); + + if (info.exists("author")) + spec.author = info.get("author"); + + if (info.exists("release")) + spec.release = info.getS32("release"); + // Attempt to load dependencies from mod.conf bool mod_conf_has_depends = false; if (info.exists("depends")) { @@ -109,6 +128,10 @@ void parseModContents(ModSpec &spec) std::vector dependencies; std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str()); + + if (is.good()) + log_mod_deprecation(spec, "depends.txt is deprecated, please use mod.conf instead."); + while (is.good()) { std::string dep; std::getline(is, dep); @@ -127,14 +150,10 @@ void parseModContents(ModSpec &spec) } } - if (info.exists("description")) { + if (info.exists("description")) spec.desc = info.get("description"); - } else { - std::ifstream is((spec.path + DIR_DELIM + "description.txt") - .c_str()); - spec.desc = std::string((std::istreambuf_iterator(is)), - std::istreambuf_iterator()); - } + else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc)) + log_mod_deprecation(spec, "description.txt is deprecated, please use mod.conf instead."); } } diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 96d197b4a..0453e1e02 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -93,7 +93,7 @@ void set_default_settings() settings->setDefault("keymap_drop", "KEY_KEY_Q"); settings->setDefault("keymap_zoom", "KEY_KEY_Z"); settings->setDefault("keymap_inventory", "KEY_KEY_I"); - settings->setDefault("keymap_special1", "KEY_KEY_E"); + settings->setDefault("keymap_aux1", "KEY_KEY_E"); settings->setDefault("keymap_chat", "KEY_KEY_T"); settings->setDefault("keymap_cmd", "/"); settings->setDefault("keymap_cmd_local", "."); @@ -529,7 +529,7 @@ void set_default_settings() settings->setDefault("touchtarget", "true"); settings->setDefault("touchscreen_threshold", "20"); settings->setDefault("fixed_virtual_joystick", "true"); - settings->setDefault("virtual_joystick_triggers_aux", "false"); + settings->setDefault("virtual_joystick_triggers_aux1", "false"); #endif // Mobile Platform diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index a4038950d..798b8e210 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -47,7 +47,7 @@ enum GUI_ID_KEY_BACKWARD_BUTTON, GUI_ID_KEY_LEFT_BUTTON, GUI_ID_KEY_RIGHT_BUTTON, - GUI_ID_KEY_USE_BUTTON, + GUI_ID_KEY_AUX1_BUTTON, GUI_ID_KEY_FLY_BUTTON, GUI_ID_KEY_FAST_BUTTON, GUI_ID_KEY_JUMP_BUTTON, @@ -184,7 +184,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { core::rect rect(0, 0, option_w, 30 * s); rect += topleft + v2s32(option_x, option_y); - const wchar_t *text = wgettext("\"Special\" = climb down"); + const wchar_t *text = wgettext("\"Aux1\" = climb down"); Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this, GUI_ID_CB_AUX1_DESCENDS, text); delete[] text; @@ -423,7 +423,7 @@ void GUIKeyChangeMenu::init_keys() this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward"); this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left"); this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right"); - this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Special"), "keymap_special1"); + this->add_key(GUI_ID_KEY_AUX1_BUTTON, wgettext("Aux1"), "keymap_aux1"); this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump"); this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak"); this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop"); diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index 68ffec70f..3ba88445b 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -43,7 +43,7 @@ const char **button_imagenames = (const char *[]) { "drop_btn.png", "down_btn.png", //"zoom.png", - //"aux_btn.png", + //"aux1_btn.png", "inventory_btn.png", "escape_btn.png", "minimap_btn.png", @@ -89,8 +89,8 @@ static irr::EKEY_CODE id2keycode(touch_gui_button_id id) /*case zoom_id: key = "zoom"; break; - case special1_id: - key = "special1"; + case aux1_id: + key = "aux1"; break;*/ case fly_id: key = "freemove"; @@ -437,7 +437,7 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver) m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold"); m_mouse_sensitivity = rangelim(g_settings->getFloat("mouse_sensitivity"), 0.1, 1.0); m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); - m_joystick_triggers_special1 = g_settings->getBool("virtual_joystick_triggers_aux"); + m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1"); m_screensize = m_device->getVideoDriver()->getScreenSize(); button_size = std::min(m_screensize.Y / 4.5f, RenderingEngine::getDisplayDensity() * @@ -549,9 +549,9 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) m_screensize.Y - (3 * button_size)), L"z", false); - // init special1/aux button - if (!m_joystick_triggers_special1) - initButton(special1_id, + // init aux1 button + if (!m_joystick_triggers_aux1) + initButton(aux1_id, rect(m_screensize.X - (1.25 * button_size), m_screensize.Y - (2.5 * button_size), m_screensize.X - (0.25 * button_size), @@ -845,7 +845,7 @@ void TouchScreenGUI::moveJoystick(const SEvent &event, float dx, float dy) { } if (distance > button_size * 1.5) { - m_joystick_status[j_special1] = true; + m_joystick_status[j_aux1] = true; // move joystick "button" s32 ndx = button_size * dx / distance * 1.5f - button_size / 2.0f * 1.5f; s32 ndy = button_size * dy / distance * 1.5f - button_size / 2.0f * 1.5f; @@ -1124,7 +1124,7 @@ bool TouchScreenGUI::quickTapDetection() void TouchScreenGUI::applyJoystickStatus() { for (u32 i = 0; i < 5; i++) { - if (i == 4 && !m_joystick_triggers_special1) + if (i == 4 && !m_joystick_triggers_aux1) continue; SEvent translated{}; diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h index 742c55a33..4242065c9 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchscreengui.h @@ -42,7 +42,7 @@ typedef enum crunch_id, inventory_id, // zoom_id, - // special1_id, + // aux1_id, escape_id, minimap_id, range_id, @@ -71,7 +71,7 @@ typedef enum j_backward, j_left, j_right, - j_special1 + j_aux1 } touch_gui_joystick_move_id; typedef enum @@ -224,7 +224,7 @@ private: // forward, backward, left, right touch_gui_button_id m_joystick_names[5] = { - forward_id, backward_id, left_id, right_id, /*special1_id*/}; + forward_id, backward_id, left_id, right_id, /*aux1_id*/}; bool m_joystick_status[5] = {false, false, false, false, false}; /* @@ -246,7 +246,7 @@ private: size_t m_joystick_id; bool m_joystick_has_really_moved = false; bool m_fixed_joystick = false; - bool m_joystick_triggers_special1 = false; + bool m_joystick_triggers_aux1 = false; button_info *m_joystick_btn_off = nullptr; button_info *m_joystick_btn_bg = nullptr; button_info *m_joystick_btn_center = nullptr; diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 55eab522a..50deacb24 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_settings.h" #include "lua_api/l_internal.h" #include "cpp_api/s_security.h" +#include "threading/mutex_auto_lock.h" #include "util/string.h" // FlagDesc #include "settings.h" #include "noise.h" @@ -291,20 +292,36 @@ int LuaSettings::l_write(lua_State* L) return 1; } +static void push_settings_table(lua_State *L, const Settings *settings) +{ + std::vector keys = settings->getNames(); + lua_newtable(L); + for (const std::string &key : keys) { + std::string value; + Settings *group = nullptr; + + if (settings->getNoEx(key, value)) { + lua_pushstring(L, value.c_str()); + } else if (settings->getGroupNoEx(key, group)) { + // Recursively push tables + push_settings_table(L, group); + } else { + // Impossible case (multithreading) due to MutexAutoLock + continue; + } + + lua_setfield(L, -2, key.c_str()); + } +} + // to_table(self) -> {[key1]=value1,...} int LuaSettings::l_to_table(lua_State* L) { NO_MAP_LOCK_REQUIRED; LuaSettings* o = checkobject(L, 1); - std::vector keys = o->m_settings->getNames(); - - lua_newtable(L); - for (const std::string &key : keys) { - lua_pushstring(L, o->m_settings->get(key).c_str()); - lua_setfield(L, -2, key.c_str()); - } - + MutexAutoLock(o->m_settings->m_mutex); + push_settings_table(L, o->m_settings); return 1; } diff --git a/src/server.cpp b/src/server.cpp index 8977861b5..c4bcd9dae 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2660,7 +2660,9 @@ void Server::fillMediaCache() // Collect all media file paths std::vector paths; - // The paths are ordered in descending priority + + // ordered in descending priority + paths.push_back(getBuiltinLuaPath() + DIR_DELIM + "locale"); fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server"); fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures"); m_modmgr->getModsMediaPaths(paths); diff --git a/src/settings.h b/src/settings.h index 62b01d4cd..304a25eda 100644 --- a/src/settings.h +++ b/src/settings.h @@ -239,6 +239,8 @@ private: // Allow TestSettings to run sanity checks using private functions. friend class TestSettings; + // For sane mutex locking when iterating + friend class LuaSettings; void updateNoLock(const Settings &other); void clearNoLock(); diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 873116f8e..01ac7d9e2 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -106,10 +106,6 @@ u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, f32 camera_fov, f32 range, f32 *distance_ptr) { - // Maximum radius of a block. The magic number is - // sqrt(3.0) / 2.0 in literal form. - static constexpr const f32 block_max_radius = 0.866025403784f * MAP_BLOCKSIZE * BS; - v3s16 blockpos_nodes = blockpos_b * MAP_BLOCKSIZE; // Block center position @@ -129,19 +125,19 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, *distance_ptr = d; // If block is far away, it's not in sight - if (d > range + block_max_radius) + if (d > range + BLOCK_MAX_RADIUS) return false; // If block is (nearly) touching the camera, don't // bother validating further (that is, render it anyway) - if (d <= block_max_radius) + if (d <= BLOCK_MAX_RADIUS) return true; // Adjust camera position, for purposes of computing the angle, // such that a block that has any portion visible with the // current camera position will have the center visible at the // adjusted postion - f32 adjdist = block_max_radius / cos((M_PI - camera_fov) / 2); + f32 adjdist = BLOCK_MAX_RADIUS / cos((M_PI - camera_fov) / 2); // Block position relative to adjusted camera v3f blockpos_adj = blockpos - (camera_pos - camera_dir * adjdist); diff --git a/src/util/numeric.h b/src/util/numeric.h index 06f801f85..d698daa2d 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "basic_macros.h" +#include "constants.h" #include "irrlichttypes.h" #include "irr_v2d.h" #include "irr_v3d.h" @@ -36,6 +37,9 @@ with this program; if not, write to the Free Software Foundation, Inc., y = temp; \ } while (0) +// Maximum radius of a block. The magic number is +// sqrt(3.0) / 2.0 in literal form. +static constexpr const f32 BLOCK_MAX_RADIUS = 0.866025403784f * MAP_BLOCKSIZE * BS; inline s16 getContainerPos(s16 p, s16 d) { diff --git a/textures/base/pack/aux1_btn.png b/textures/base/pack/aux1_btn.png new file mode 100644 index 000000000..8ceb09542 Binary files /dev/null and b/textures/base/pack/aux1_btn.png differ diff --git a/textures/base/pack/aux_btn.png b/textures/base/pack/aux_btn.png deleted file mode 100644 index 0f47b7a0f..000000000 Binary files a/textures/base/pack/aux_btn.png and /dev/null differ diff --git a/util/ci/lint.sh b/util/ci/clang-format.sh similarity index 71% rename from util/ci/lint.sh rename to util/ci/clang-format.sh index 395445ca7..89576c656 100755 --- a/util/ci/lint.sh +++ b/util/ci/clang-format.sh @@ -1,6 +1,6 @@ #! /bin/bash -function perform_lint() { - echo "Performing LINT..." + +function setup_for_format() { if [ -z "${CLANG_FORMAT}" ]; then CLANG_FORMAT=clang-format fi @@ -8,6 +8,12 @@ function perform_lint() { CLANG_FORMAT_WHITELIST="util/ci/clang-format-whitelist.txt" files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')" +} + +function check_format() { + echo "Checking format..." + + setup_for_format local errorcount=0 local fail=0 @@ -41,3 +47,18 @@ function perform_lint() { echo "LINT OK" } + + +function fix_format() { + echo "Fixing format..." + + setup_for_format + + for f in ${files_to_lint}; do + whitelisted=$(awk '$1 == "'$f'" { print 1 }' "$CLANG_FORMAT_WHITELIST") + if [ -z "${whitelisted}" ]; then + echo "$f" + $CLANG_FORMAT -i "$f" + fi + done +} diff --git a/util/fix_format.sh b/util/fix_format.sh new file mode 100755 index 000000000..3cef6f58d --- /dev/null +++ b/util/fix_format.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e + +. ./util/ci/clang-format.sh + +fix_format diff --git a/util/updatepo.sh b/util/updatepo.sh index 168483bd4..95acb01ea 100755 --- a/util/updatepo.sh +++ b/util/updatepo.sh @@ -58,6 +58,7 @@ xgettext --package-name=minetest \ --keyword=fgettext_ne \ --keyword=strgettext \ --keyword=wstrgettext \ + --keyword=core.gettext \ --keyword=showTranslatedStatusText \ --output $potfile \ --from-code=utf-8 \