1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-07-02 16:38:41 +00:00

Merge branch 'master' into master

This commit is contained in:
DustyBagel 2025-05-24 11:19:16 -05:00 committed by GitHub
commit 80c0410442
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
769 changed files with 137450 additions and 85683 deletions

View file

@ -32,8 +32,8 @@ do
all.registered_craftitems = {}
all.registered_tools = {}
for k, v in pairs(all.registered_items) do
-- Disable further modification
setmetatable(v, {__newindex = {}})
-- Ignore new keys
setmetatable(v, {__newindex = function() end})
-- Reassemble the other tables
if v.type == "node" then
getmetatable(v).__index = all.nodedef_default
@ -59,6 +59,9 @@ end
local alias_metatable = {
__index = function(t, name)
return rawget(t, core.registered_aliases[name])
end,
__newindex = function()
error("table is read-only")
end
}
setmetatable(core.registered_items, alias_metatable)

View file

@ -1,5 +1,6 @@
core.log("info", "Initializing asynchronous environment")
function core.job_processor(func, serialized_param)
local param = core.deserialize(serialized_param)
@ -7,3 +8,15 @@ function core.job_processor(func, serialized_param)
return retval or core.serialize(nil)
end
function core.get_http_accept_languages()
local languages
local current_language = core.get_language()
if current_language ~= "" then
languages = { current_language, "en;q=0.8" }
else
languages = { "en" }
end
return "Accept-Language: " .. table.concat(languages, ", ")
end

View file

@ -2,7 +2,10 @@ local scriptpath = core.get_builtin_path()
local clientpath = scriptpath.."client"..DIR_DELIM
local commonpath = scriptpath.."common"..DIR_DELIM
dofile(clientpath .. "register.lua")
local builtin_shared = {}
assert(loadfile(commonpath .. "register.lua"))(builtin_shared)
assert(loadfile(clientpath .. "register.lua"))(builtin_shared)
dofile(commonpath .. "after.lua")
dofile(commonpath .. "mod_storage.lua")
dofile(commonpath .. "chatcommands.lua")

View file

@ -1,68 +1,6 @@
core.callback_origins = {}
local builtin_shared = ...
local getinfo = debug.getinfo
debug.getinfo = nil
--- Runs given callbacks.
--
-- Note: this function is also called from C++
-- @tparam table callbacks a table with registered callbacks, like `core.registered_on_*`
-- @tparam number mode a RunCallbacksMode, as defined in src/script/common/c_internal.h
-- @param ... arguments for the callback
-- @return depends on mode
function core.run_callbacks(callbacks, mode, ...)
assert(type(callbacks) == "table")
local cb_len = #callbacks
if cb_len == 0 then
if mode == 2 or mode == 3 then
return true
elseif mode == 4 or mode == 5 then
return false
end
end
local ret
for i = 1, cb_len do
local cb_ret = callbacks[i](...)
if mode == 0 and i == 1 or mode == 1 and i == cb_len then
ret = cb_ret
elseif mode == 2 then
if not cb_ret or i == 1 then
ret = cb_ret
end
elseif mode == 3 then
if cb_ret then
return cb_ret
end
ret = cb_ret
elseif mode == 4 then
if (cb_ret and not ret) or i == 1 then
ret = cb_ret
end
elseif mode == 5 and cb_ret then
return cb_ret
end
end
return ret
end
--
-- Callback registration
--
local function make_registration()
local t = {}
local registerfunc = function(func)
t[#t + 1] = func
core.callback_origins[func] = {
mod = core.get_current_modname() or "??",
name = getinfo(1, "n").name or "??"
}
--local origin = core.callback_origins[func]
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
end
return t, registerfunc
end
local make_registration = builtin_shared.make_registration
core.registered_globalsteps, core.register_globalstep = make_registration()
core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration()

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2013 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
--------------------------------------------------------------------------------
-- TODO improve doc --

View file

@ -90,7 +90,7 @@ local facedir_to_dir_map = {
1, 4, 3, 2,
}
function core.facedir_to_dir(facedir)
return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
return vector.copy(facedir_to_dir[facedir_to_dir_map[facedir % 32]])
end
function core.dir_to_fourdir(dir)
@ -110,7 +110,7 @@ function core.dir_to_fourdir(dir)
end
function core.fourdir_to_dir(fourdir)
return facedir_to_dir[facedir_to_dir_map[fourdir % 4]]
return vector.copy(facedir_to_dir[facedir_to_dir_map[fourdir % 4]])
end
function core.dir_to_wallmounted(dir)
@ -147,7 +147,7 @@ local wallmounted_to_dir = {
vector.new( 0, -1, 0),
}
function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8]
return vector.copy(wallmounted_to_dir[wallmounted % 8])
end
function core.dir_to_yaw(dir)

15
builtin/common/menu.lua Normal file
View file

@ -0,0 +1,15 @@
-- Luanti
-- SPDX-License-Identifier: LGPL-2.1-or-later
-- These colors are used by the main menu and the settings menu
mt_color_grey = "#AAAAAA"
mt_color_blue = "#6389FF"
mt_color_lightblue = "#99CCFF"
mt_color_green = "#72FF63"
mt_color_dark_green = "#25C191"
mt_color_orange = "#FF8800"
mt_color_red = "#FF3300"
function core.are_keycodes_equal(k1, k2)
return core.normalize_keycode(k1) == core.normalize_keycode(k2)
end

View file

@ -7,18 +7,21 @@ local math = math
local function basic_dump(o)
local tp = type(o)
if tp == "number" then
return tostring(o)
local s = tostring(o)
if tonumber(s) == o then
return s
end
-- Prefer an exact representation over a compact representation.
-- e.g. basic_dump(0.3) == "0.3",
-- but basic_dump(0.1 + 0.2) == "0.30000000000000004"
-- so the user can see that 0.1 + 0.2 ~= 0.3
return string.format("%.17g", o)
elseif tp == "string" then
return string.format("%q", o)
elseif tp == "boolean" then
return tostring(o)
elseif tp == "nil" then
return "nil"
-- Uncomment for full function dumping support.
-- Not currently enabled because bytecode isn't very human-readable and
-- dump's output is intended for humans.
--elseif tp == "function" then
-- return string.format("loadstring(%q)", string.dump(o))
elseif tp == "userdata" then
return tostring(o)
else
@ -105,65 +108,141 @@ function dump2(o, name, dumped)
return string.format("%s = {}\n%s", name, table.concat(t))
end
--------------------------------------------------------------------------------
-- This dumps values in a one-statement format.
-- This dumps values in a human-readable expression format.
-- If possible, the resulting string should evaluate to an equivalent value if loaded and executed.
-- For example, {test = {"Testing..."}} becomes:
-- [[{
-- test = {
-- "Testing..."
-- }
-- }]]
-- This supports tables as keys, but not circular references.
-- It performs poorly with multiple references as it writes out the full
-- table each time.
-- The indent field specifies a indentation string, it defaults to a tab.
-- Use the empty string to disable indentation.
-- The dumped and level arguments are internal-only.
function dump(o, indent, nested, level)
local t = type(o)
if not level and t == "userdata" then
-- when userdata (e.g. player) is passed directly, print its metatable:
return "userdata metatable: " .. dump(getmetatable(o))
end
if t ~= "table" then
return basic_dump(o)
end
-- Contains table -> true/nil of currently nested tables
nested = nested or {}
if nested[o] then
return "<circular reference>"
end
nested[o] = true
function dump(value, indent)
indent = indent or "\t"
level = level or 1
local newline = indent == "" and "" or "\n"
local ret = {}
local dumped_indexes = {}
for i, v in ipairs(o) do
ret[#ret + 1] = dump(v, indent, nested, level + 1)
dumped_indexes[i] = true
end
for k, v in pairs(o) do
if not dumped_indexes[k] then
if type(k) ~= "string" or not is_valid_identifier(k) then
k = "["..dump(k, indent, nested, level + 1).."]"
end
v = dump(v, indent, nested, level + 1)
ret[#ret + 1] = k.." = "..v
local rope = {}
local write
do
-- Keeping the length of the table as a local variable is *much*
-- faster than invoking the length operator.
-- See https://gitspartv.github.io/LuaJIT-Benchmarks/#test12.
local i = 0
function write(str)
i = i + 1
rope[i] = str
end
end
nested[o] = nil
if indent ~= "" then
local indent_str = "\n"..string.rep(indent, level)
local end_indent_str = "\n"..string.rep(indent, level - 1)
return string.format("{%s%s%s}",
indent_str,
table.concat(ret, ","..indent_str),
end_indent_str)
local n_refs = {}
local function count_refs(val)
if type(val) ~= "table" then
return
end
local tbl = val
if n_refs[tbl] then
n_refs[tbl] = n_refs[tbl] + 1
return
end
n_refs[tbl] = 1
for k, v in pairs(tbl) do
count_refs(k)
count_refs(v)
end
end
return "{"..table.concat(ret, ", ").."}"
count_refs(value)
local refs = {}
local cur_ref = 1
local function write_value(val, level)
if type(val) ~= "table" then
write(basic_dump(val))
return
end
local tbl = val
if refs[tbl] then
write(refs[tbl])
return
end
if n_refs[val] > 1 then
refs[val] = ("getref(%d)"):format(cur_ref)
write(("setref(%d)"):format(cur_ref))
cur_ref = cur_ref + 1
end
write("{")
if next(tbl) == nil then
write("}")
return
end
write(newline)
local function write_entry(k, v)
write(indent:rep(level))
write("[")
write_value(k, level + 1)
write("] = ")
write_value(v, level + 1)
write(",")
write(newline)
end
local keys = {string = {}, number = {}}
for k in pairs(tbl) do
local t = type(k)
if keys[t] then
table.insert(keys[t], k)
end
end
-- Write string-keyed entries
table.sort(keys.string)
for _, k in ipairs(keys.string) do
local v = val[k]
if is_valid_identifier(k) then
write(indent:rep(level))
write(k)
write(" = ")
write_value(v, level + 1)
write(",")
write(newline)
else
write_entry(k, v)
end
end
-- Write number-keyed entries
local len = 0
for i in ipairs(tbl) do
len = i
end
if #keys.number == len then -- table is a list
for _, v in ipairs(tbl) do
write(indent:rep(level))
write_value(v, level + 1)
write(",")
write(newline)
end
else -- table harbors arbitrary number keys
table.sort(keys.number)
for _, k in ipairs(keys.number) do
write_entry(k, tbl[k])
end
end
-- Write all remaining entries
for k, v in pairs(val) do
if not keys[type(k)] then
write_entry(k, v)
end
end
write(indent:rep(level - 1))
write("}")
end
write_value(value, 1)
return table.concat(rope)
end
--------------------------------------------------------------------------------
@ -457,18 +536,37 @@ do
end
end
--------------------------------------------------------------------------------
function table.copy(t, seen)
local n = {}
seen = seen or {}
seen[t] = n
for k, v in pairs(t) do
n[(type(k) == "table" and (seen[k] or table.copy(k, seen))) or k] =
(type(v) == "table" and (seen[v] or table.copy(v, seen))) or v
local function table_copy(value, preserve_metatables)
local seen = {}
local function copy(val)
if type(val) ~= "table" then
return val
end
local t = val
if seen[t] then
return seen[t]
end
local res = {}
seen[t] = res
for k, v in pairs(t) do
res[copy(k)] = copy(v)
end
if preserve_metatables then
setmetatable(res, getmetatable(t))
end
return res
end
return n
return copy(value)
end
function table.copy(value)
return table_copy(value, false)
end
function table.copy_with_metatables(value)
return table_copy(value, true)
end
function table.insert_all(t, other)
if table.move then -- LuaJIT
@ -536,6 +634,10 @@ if core.gettext then -- for client and mainmenu
function fgettext(text, ...)
return core.formspec_escape(fgettext_ne(text, ...))
end
function hgettext(text, ...)
return core.hypertext_escape(fgettext_ne(text, ...))
end
end
local ESCAPE_CHAR = string.char(0x1b)

View file

@ -54,7 +54,8 @@ function builtin_shared.make_registration()
local registerfunc = function(func)
t[#t + 1] = func
core.callback_origins[func] = {
mod = core.get_current_modname() or "??",
-- may be nil or return nil
mod = core.get_current_modname and core.get_current_modname() or "??",
name = debug.getinfo(1, "n").name or "??"
}
end
@ -66,7 +67,8 @@ function builtin_shared.make_registration_reverse()
local registerfunc = function(func)
table.insert(t, 1, func)
core.callback_origins[func] = {
mod = core.get_current_modname() or "??",
-- may be nil or return nil
mod = core.get_current_modname and core.get_current_modname() or "??",
name = debug.getinfo(1, "n").name or "??"
}
end

View file

@ -190,11 +190,41 @@ local function serialize(value, write)
dump(value)
end
-- Whether `value` recursively contains a function
local function contains_function(value)
local seen = {}
local function check(val)
if type(val) == "function" then
return true
end
if type(val) == "table" then
if seen[val] then
return false
end
seen[val] = true
for k, v in pairs(val) do
if check(k) or check(v) then
return true
end
end
end
return false
end
return check(value)
end
function core.serialize(value)
if contains_function(value) then
core.log("deprecated", "Support for dumping functions in `core.serialize` is deprecated.")
end
local rope = {}
-- Keeping the length of the table as a local variable is *much*
-- faster than invoking the length operator.
-- See https://gitspartv.github.io/LuaJIT-Benchmarks/#test12.
local i = 0
serialize(value, function(text)
-- Faster than table.insert(rope, text) on PUC Lua 5.1
rope[#rope + 1] = text
i = i + 1
rope[i] = text
end)
return table_concat(rope)
end

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2022 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2022 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local make = {}
@ -37,6 +24,7 @@ local make = {}
-- * `fs` is a string for the formspec.
-- Components should be relative to `0,0`, and not exceed `avail_w` or the returned `used_height`.
-- * `used_height` is the space used by components in `fs`.
-- * `spacing`: (Optional) the vertical margin to be added before the component (default 0.25)
-- * `on_submit = function(self, fields, parent)`:
-- * `fields`: submitted formspec fields
-- * `parent`: the fstk element for the settings UI, use to show dialogs
@ -98,6 +86,7 @@ local function make_field(converter, validator, stringifier)
local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format(
avail_w - 1.5, setting.name, get_label(setting), core.formspec_escape(value))
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name)
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env
fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set"))
return fs, 1.1
@ -217,6 +206,8 @@ local function make_path(setting)
local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format(
avail_w - 3, setting.name, get_label(setting), core.formspec_escape(value))
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name)
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name) -- for pause menu env
fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 3, "pick_" .. setting.name, fgettext("Browse"))
fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set"))
@ -249,8 +240,11 @@ local function make_path(setting)
}
end
if PLATFORM == "Android" then
if PLATFORM == "Android" or INIT == "pause_menu" then
-- The Irrlicht file picker doesn't work on Android.
-- Access to the Irrlicht file picker isn't implemented in the pause menu.
-- We want to delete the Irrlicht file picker anyway, so any time spent on
-- that would be wasted.
make.path = make.string
make.filepath = make.string
else
@ -282,6 +276,14 @@ function make.v3f(setting)
fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format(
2 * (field_width + 0.25), field_width, setting.name .. "_z", "Z", value.z)
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_x")
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_y")
fs = fs .. ("field_enter_after_edit[%s;true]"):format(setting.name .. "_z")
-- for pause menu env
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_x")
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_y")
fs = fs .. ("field_close_on_enter[%s;false]"):format(setting.name .. "_z")
fs = fs .. ("button[%f,0.6;1,0.8;%s;%s]"):format(avail_w, "set_" .. setting.name, fgettext("Set"))
return fs, 1.4
@ -428,8 +430,75 @@ local function make_noise_params(setting)
}
end
make.noise_params_2d = make_noise_params
make.noise_params_3d = make_noise_params
function make.key(setting)
local btn_bind = "bind_" .. setting.name
local btn_clear = "unbind_" .. setting.name
local function add_conflict_warnings(fs, height)
local value = core.settings:get(setting.name)
if value == "" then
return height
end
for _, o in ipairs(core.full_settingtypes) do
if o.type == "key" and o.name ~= setting.name and core.are_keycodes_equal(core.settings:get(o.name), value) then
table.insert(fs, ("label[0,%f;%s]"):format(height + 0.3,
core.colorize(mt_color_orange, fgettext([[Conflicts with "$1"]], fgettext(o.readable_name)))))
height = height + 0.6
end
end
return height
end
return {
info_text = setting.comment,
setting = setting,
spacing = 0.1,
get_formspec = function(self, avail_w)
self.resettable = core.settings:has(setting.name)
local btn_bind_width = math.max(2.5, avail_w/2)
local value = core.settings:get(setting.name)
local fs = {
("label[0,0.4;%s]"):format(get_label(setting)),
("button_key[%f,0;%f,0.8;%s;%s]"):format(
btn_bind_width, btn_bind_width-0.8,
btn_bind, core.formspec_escape(value)),
("image_button[%f,0;0.8,0.8;%s;%s;]"):format(avail_w - 0.8,
core.formspec_escape(defaulttexturedir .. "clear.png"),
btn_clear),
("tooltip[%s;%s]"):format(btn_clear, fgettext("Remove keybinding")),
}
local height = 0.8
height = add_conflict_warnings(fs, height)
return table.concat(fs), height
end,
on_submit = function(self, fields)
if fields[btn_bind] then
core.settings:set(setting.name, fields[btn_bind])
return true
elseif fields[btn_clear] then
core.settings:set(setting.name, "")
return true
end
end,
}
end
if INIT == "pause_menu" then
-- Making the noise parameter dialog work in the pause menu settings would
-- require porting "FSTK" (at least the dialog API) from the mainmenu formspec
-- API to the in-game formspec API.
-- There's no reason you'd want to adjust mapgen noise parameter settings
-- in-game (they only apply to new worlds, hidden as [world_creation]),
-- so there's no reason to implement this.
local empty = function()
return { get_formspec = function() return "", 0 end }
end
make.noise_params_2d = empty
make.noise_params_3d = empty
else
make.noise_params_2d = make_noise_params
make.noise_params_3d = make_noise_params
end
return make

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2015 PilzAdam
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2015 PilzAdam
-- SPDX-License-Identifier: LGPL-2.1-or-later
local checkboxes = {}

View file

@ -1,29 +1,14 @@
--Luanti
--Copyright (C) 2022 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2022 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
"settings" .. DIR_DELIM .. "components.lua")
local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM
local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
"settings" .. DIR_DELIM .. "shadows_component.lua")
local component_funcs = dofile(path .. "components.lua")
local shadows_component = dofile(path .. "shadows_component.lua")
local loaded = false
local full_settings
local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png")
local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png")
local all_pages = {}
@ -33,7 +18,7 @@ local filtered_page_by_id = page_by_id
local function get_setting_info(name)
for _, entry in ipairs(full_settings) do
for _, entry in ipairs(core.full_settingtypes) do
if entry.type ~= "category" and entry.name == name then
return entry
end
@ -71,7 +56,7 @@ local function load_settingtypes()
end
end
for _, entry in ipairs(full_settings) do
for _, entry in ipairs(core.full_settingtypes) do
if entry.type == "category" then
if entry.level == 0 then
section = entry.name
@ -105,29 +90,14 @@ local function load()
end
loaded = true
full_settings = settingtypes.parse_config_file(false, true)
local change_keys = {
query_text = "Controls",
requires = {
touch_controls = false,
},
get_formspec = function(self, avail_w)
local btn_w = math.min(avail_w, 3)
return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8
end,
on_submit = function(self, fields)
if fields.btn_change_keys then
core.show_keys_menu()
end
end,
}
core.full_settingtypes = settingtypes.parse_config_file(false, true)
local touchscreen_layout = {
query_text = "Touchscreen layout",
requires = {
touchscreen = true,
},
context = "client",
get_formspec = function(self, avail_w)
local btn_w = math.min(avail_w, 6)
return ("button[0,0;%f,0.8;btn_touch_layout;%s]"):format(btn_w, fgettext("Touchscreen layout")), 0.8
@ -160,13 +130,11 @@ local function load()
{ heading = fgettext_ne("Movement") },
"arm_inertia",
"view_bobbing_amount",
"fall_bobbing_amount",
},
})
load_settingtypes()
table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys)
-- insert after "touch_controls"
table.insert(page_by_id.controls_touchscreen.content, 2, touchscreen_layout)
do
@ -175,18 +143,24 @@ local function load()
table.insert(content, idx, shadows_component)
idx = table.indexof(content, "enable_auto_exposure") + 1
local setting_info = get_setting_info("enable_auto_exposure")
local note = component_funcs.note(fgettext_ne("(The game will need to enable automatic exposure as well)"))
note.requires = get_setting_info("enable_auto_exposure").requires
note.requires = setting_info.requires
note.context = setting_info.context
table.insert(content, idx, note)
idx = table.indexof(content, "enable_bloom") + 1
setting_info = get_setting_info("enable_bloom")
note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)"))
note.requires = get_setting_info("enable_bloom").requires
note.requires = setting_info.requires
note.context = setting_info.context
table.insert(content, idx, note)
idx = table.indexof(content, "enable_volumetric_lighting") + 1
setting_info = get_setting_info("enable_volumetric_lighting")
note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)"))
note.requires = get_setting_info("enable_volumetric_lighting").requires
note.requires = setting_info.requires
note.context = setting_info.context
table.insert(content, idx, note)
end
@ -261,6 +235,17 @@ local function load()
["true"] = fgettext_ne("Enabled"),
["false"] = fgettext_ne("Disabled"),
}
get_setting_info("touch_interaction_style").option_labels = {
["tap"] = fgettext_ne("Tap"),
["tap_crosshair"] = fgettext_ne("Tap with crosshair"),
["buttons_crosshair"] = fgettext("Buttons with crosshair"),
}
get_setting_info("touch_punch_gesture").option_labels = {
["short_tap"] = fgettext_ne("Short tap"),
["long_tap"] = fgettext_ne("Long tap"),
}
end
@ -353,7 +338,18 @@ local function update_filtered_pages(query)
end
local function check_requirements(name, requires)
local shown_contexts = {
common = true,
client = true,
server = INIT ~= "pause_menu" or core.is_internal_server(),
world_creation = INIT ~= "pause_menu",
}
local function check_requirements(name, requires, context)
if context and not shown_contexts[context] then
return false
end
if requires == nil then
return true
end
@ -361,6 +357,7 @@ local function check_requirements(name, requires)
local video_driver = core.get_active_driver()
local touch_support = core.irrlicht_device_supports_touch()
local touch_controls = core.settings:get("touch_controls")
local touch_interaction_style = core.settings:get("touch_interaction_style")
local special = {
android = PLATFORM == "Android",
desktop = PLATFORM ~= "Android",
@ -371,6 +368,7 @@ local function check_requirements(name, requires)
keyboard_mouse = not touch_support or (touch_controls == "auto" or not core.is_yes(touch_controls)),
opengl = (video_driver == "opengl" or video_driver == "opengl3"),
gles = video_driver:sub(1, 5) == "ogles",
touch_interaction_style_tap = touch_interaction_style ~= "buttons_crosshair",
}
for req_key, req_value in pairs(requires) do
@ -378,6 +376,9 @@ local function check_requirements(name, requires)
local required_setting = get_setting_info(req_key)
if required_setting == nil then
core.log("warning", "Unknown setting " .. req_key .. " required by " .. (name or "???"))
elseif required_setting.type ~= "bool" then
core.log("warning", "Setting " .. req_key .. " of type " .. required_setting.type ..
" used as requirement by " .. (name or "???") .. ", only bool is allowed")
end
local actual_value = core.settings:get_bool(req_key,
required_setting and core.is_yes(required_setting.default))
@ -409,11 +410,11 @@ function page_has_contents(page, actual_content)
elseif type(item) == "string" then
local setting = get_setting_info(item)
assert(setting, "Unknown setting: " .. item)
if check_requirements(setting.name, setting.requires) then
if check_requirements(setting.name, setting.requires, setting.context) then
return true
end
elseif item.get_formspec then
if check_requirements(item.id, item.requires) then
if check_requirements(item.id, item.requires, item.context) then
return true
end
else
@ -435,20 +436,22 @@ local function build_page_components(page)
elseif item.heading then
last_heading = item
else
local name, requires
local name, requires, context
if type(item) == "string" then
local setting = get_setting_info(item)
assert(setting, "Unknown setting: " .. item)
name = setting.name
requires = setting.requires
context = setting.context
elseif item.get_formspec then
name = item.id
requires = item.requires
context = item.context
else
error("Unknown content in page: " .. dump(item))
end
if check_requirements(name, requires) then
if check_requirements(name, requires, context) then
if last_heading then
content[#content + 1] = last_heading
last_heading = nil
@ -514,7 +517,8 @@ local function get_formspec(dialogdata)
"box[0,0;", tostring(tabsize.width), ",", tostring(tabsize.height), ";#0000008C]",
("button[0,%f;%f,0.8;back;%s]"):format(
tabsize.height + 0.2, back_w, fgettext("Back")),
tabsize.height + 0.2, back_w,
fgettext("Back")),
("box[%f,%f;%f,0.8;#0000008C]"):format(
back_w + 0.2, tabsize.height + 0.2, checkbox_w),
@ -531,6 +535,7 @@ local function get_formspec(dialogdata)
"field[0.25,0.25;", tostring(search_width), ",0.75;search_query;;",
core.formspec_escape(dialogdata.query or ""), "]",
"field_enter_after_edit[search_query;true]",
"field_close_on_enter[search_query;false]", -- for pause menu env
"container[", tostring(search_width + 0.25), ", 0.25]",
"image_button[0,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
"image_button[0.75,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";search_clear;]",
@ -628,7 +633,13 @@ local function get_formspec(dialogdata)
fs[#fs + 1] = "container_end[]"
if used_h > 0 then
y = y + used_h + 0.25
local spacing = 0.25
local next_comp = dialogdata.components[i + 1]
if next_comp and next_comp.spacing then
spacing = next_comp.spacing
end
y = y + used_h + spacing
end
end
@ -671,7 +682,8 @@ local function buttonhandler(this, fields)
dialogdata.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or dialogdata.rightscroll
dialogdata.query = fields.search_query
if fields.back then
-- "fields.quit" is for the pause menu env
if fields.back or fields.quit then
this:delete()
return true
end
@ -765,11 +777,44 @@ local function eventhandler(event)
end
function create_settings_dlg()
load()
local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler)
if INIT == "mainmenu" then
function create_settings_dlg(page_id)
load()
local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler)
dlg.data.page_id = update_filtered_pages("")
dlg.data.page_id = page_id or update_filtered_pages("")
return dlg
return dlg
end
else
assert(INIT == "pause_menu")
local dialog
core.register_on_formspec_input(function(formname, fields)
if dialog and formname == "__builtin:settings" then
-- buttonhandler returning true means we should update the formspec.
-- dialog is re-checked since the buttonhandler may have closed it.
if buttonhandler(dialog, fields) and dialog then
core.show_formspec("__builtin:settings", get_formspec(dialog.data))
end
return true
end
end)
core.open_settings = function()
load()
dialog = {}
dialog.data = {}
dialog.data.page_id = update_filtered_pages("")
dialog.delete = function()
dialog = nil
-- only needed for the "fields.back" case, in the "fields.quit"
-- case it's a no-op
core.show_formspec("__builtin:settings", "")
end
core.show_formspec("__builtin:settings", get_formspec(dialog.data))
end
end

View file

@ -61,7 +61,7 @@ local function create_minetest_conf_example(settings)
end
end
if entry.type == "key" then
local line = "See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h"
local line = "See https://docs.luanti.org/for-players/controls/"
insert(result, "# " .. line .. "\n")
end
insert(result, "# type: " .. entry.type)

View file

@ -0,0 +1,15 @@
-- Luanti
-- Copyright (C) 2022 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM
dofile(path .. "settingtypes.lua")
dofile(path .. "dlg_change_mapgen_flags.lua")
dofile(path .. "dlg_settings.lua")
-- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'.
-- For RUN_IN_PLACE the generated files may appear in the 'bin' folder.
-- See comment and alternative line at the end of 'generate_from_settingtypes.lua'.
-- dofile(path .. DIR_DELIM .. "generate_from_settingtypes.lua")

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2015 PilzAdam
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2015 PilzAdam
-- SPDX-License-Identifier: LGPL-2.1-or-later
settingtypes = {}
@ -40,12 +27,24 @@ local CHAR_CLASSES = {
FLAGS = "[%w_%-%.,]",
}
local valid_contexts = {common = true, client = true, server = true, world_creation = true}
local function check_context_annotation(context, force_context)
if force_context then
return "Context annotations are not allowed, context is always " .. force_context
end
if not valid_contexts[context] then
return "Unknown context"
end
return nil
end
local function flags_to_table(flags)
return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split
end
-- returns error message, or nil
local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
local function parse_setting_line(settings, line, read_all, base_level, allow_secure, force_context)
-- strip carriage returns (CR, /r)
line = line:gsub("\r", "")
@ -69,9 +68,32 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
-- category
local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
local category_context
if not category then
stars, category, category_context = line:match("^%[([%*]*)([^%]]+)%] %[([^%]]+)%]$")
end
if category then
local category_level = stars:len() + base_level
if settings.current_context_level and
category_level <= settings.current_context_level then
-- The start of this category marks the end of the context annotation's scope.
settings.current_context_level = nil
settings.current_context = nil
end
if category_context then
local err = check_context_annotation(category_context, force_context)
if err then
return err
end
if settings.current_context_level then
return "Category context annotations cannot be nested"
end
settings.current_context_level = category_level
settings.current_context = category_context
end
if settings.current_hide_level then
if settings.current_hide_level < category_level then
-- Skip this category, it's inside a hidden category.
@ -102,7 +124,8 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
end
-- settings
local first_part, name, readable_name, setting_type = line:match("^"
local function make_pattern(include_context)
return "^"
-- this first capture group matches the whole first part,
-- so we can later strip it from the rest of the line
.. "("
@ -110,9 +133,19 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
.. CHAR_CLASSES.SPACE .. "*"
.. "%(([^%)]*)%)" -- readable name
.. CHAR_CLASSES.SPACE .. "*"
.. (include_context and (
"%[([^%]]+)%]" -- context annotation
.. CHAR_CLASSES.SPACE .. "*"
) or "")
.. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type
.. CHAR_CLASSES.SPACE .. "*"
.. ")")
.. ")"
end
local first_part, name, readable_name, setting_type = line:match(make_pattern(false))
local setting_context
if not first_part then
first_part, name, readable_name, setting_context, setting_type = line:match(make_pattern(true))
end
if not first_part then
return "Invalid line"
@ -122,6 +155,26 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
return "Tried to add \"secure.\" setting"
end
if setting_context then
local err = check_context_annotation(setting_context, force_context)
if err then
return err
end
end
local context
if force_context then
context = force_context
else
if setting_context then
context = setting_context
elseif settings.current_context_level then
context = settings.current_context
else
return "Missing context annotation"
end
end
local requires = {}
local last_line = #current_comment > 0 and current_comment[#current_comment]:trim()
if last_line and last_line:lower():sub(1, 9) == "requires:" then
@ -170,6 +223,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
min = min,
max = max,
requires = requires,
context = context,
comment = comment,
})
return
@ -182,9 +236,9 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
if not default then
return "Invalid string setting"
end
if setting_type == "key" and not read_all then
-- ignore key type if read_all is false
return
if setting_type == "key" then
requires.keyboard_mouse = true
end
table.insert(settings, {
@ -193,6 +247,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
type = setting_type,
default = default,
requires = requires,
context = context,
comment = comment,
})
return
@ -245,6 +300,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
},
values = values,
requires = requires,
context = context,
comment = comment,
noise_params = true,
flags = flags_to_table("defaults,eased,absvalue")
@ -263,6 +319,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
type = "bool",
default = remaining_line,
requires = requires,
context = context,
comment = comment,
})
return
@ -290,6 +347,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
min = min,
max = max,
requires = requires,
context = context,
comment = comment,
})
return
@ -313,6 +371,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
default = default,
values = values:split(",", true),
requires = requires,
context = context,
comment = comment,
})
return
@ -331,6 +390,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
type = setting_type,
default = default,
requires = requires,
context = context,
comment = comment,
})
return
@ -361,6 +421,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
default = default,
possible = flags_to_table(possible),
requires = requires,
context = context,
comment = comment,
})
return
@ -369,14 +430,14 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
return "Invalid setting type \"" .. setting_type .. "\""
end
local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure, force_context)
-- store this helper variable in the table so it's easier to pass to parse_setting_line()
result.current_comment = {}
result.current_hide_level = nil
local line = file:read("*line")
while line do
local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure)
local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure, force_context)
if error_msg then
core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
end
@ -408,7 +469,15 @@ function settingtypes.parse_config_file(read_all, parse_mods)
file:close()
end
if parse_mods then
-- TODO: Support game/mod settings in the pause menu too
-- Note that this will need to work different from how it's done in the
-- mainmenu:
-- * Only show settings for the active game and mods
-- (add API function to get them, can return nil if on a remote server)
-- (names are probably not enough, will need paths for uniqueness)
-- This means just making "pkgmgr.lua" work won't get you very far.
if INIT == "mainmenu" and parse_mods then
-- Parse games
local games_category_initialized = false
for _, game in ipairs(pkgmgr.games) do
@ -432,7 +501,7 @@ function settingtypes.parse_config_file(read_all, parse_mods)
type = "category",
})
parse_single_file(file, path, read_all, settings, 2, false)
parse_single_file(file, path, read_all, settings, 2, false, "server")
file:close()
end
@ -465,7 +534,7 @@ function settingtypes.parse_config_file(read_all, parse_mods)
type = "category",
})
parse_single_file(file, path, read_all, settings, 2, false)
parse_single_file(file, path, read_all, settings, 2, false, "server")
file:close()
end
@ -496,7 +565,7 @@ function settingtypes.parse_config_file(read_all, parse_mods)
type = "category",
})
parse_single_file(file, path, read_all, settings, 2, false)
parse_single_file(file, path, read_all, settings, 2, false, "client")
file:close()
end

View file

@ -1,20 +1,7 @@
--Luanti
--Copyright (C) 2021-2 x2048
--Copyright (C) 2022-3 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2021-2 x2048
-- Copyright (C) 2022-3 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local shadow_levels_labels = {
@ -84,6 +71,7 @@ return {
requires = {
opengl = true,
},
context = "client",
get_formspec = function(self, avail_w)
local labels = table.copy(shadow_levels_labels)
local idx = detect_mapping_idx()

View file

@ -178,6 +178,35 @@ describe("table", function()
assert.equal(2, table.keyof({[2] = "foo", [3] = "bar"}, "foo"))
assert.equal(3, table.keyof({[1] = "foo", [3] = "bar"}, "bar"))
end)
describe("copy()", function()
it("strips metatables", function()
local v = vector.new(1, 2, 3)
local w = table.copy(v)
assert.are_not.equal(v, w)
assert.same(v, w)
assert.equal(nil, getmetatable(w))
end)
it("preserves referential structure", function()
local t = {{}, {}}
t[1][1] = t[2]
t[2][1] = t[1]
local copy = table.copy(t)
assert.same(t, copy)
assert.equal(copy[1][1], copy[2])
assert.equal(copy[2][1], copy[1])
end)
end)
describe("copy_with_metatables()", function()
it("preserves metatables", function()
local v = vector.new(1, 2, 3)
local w = table.copy_with_metatables(v)
assert.equal(getmetatable(v), getmetatable(w))
assert(vector.check(w))
assert.equal(v, w) -- vector overrides ==
end)
end)
end)
describe("formspec_escape", function()
@ -201,3 +230,124 @@ describe("math", function()
assert.equal(0, math.round(-0.49999999999999994))
end)
end)
describe("dump", function()
local function test_expression(expr)
local chunk = assert(loadstring("return " .. expr))
local refs = {}
setfenv(chunk, {
setref = function(id)
refs[id] = {}
return function(fields)
for k, v in pairs(fields) do
refs[id][k] = v
end
return refs[id]
end
end,
getref = function(id)
return assert(refs[id])
end,
})
assert.equal(expr, dump(chunk()))
end
it("nil", function()
test_expression("nil")
end)
it("booleans", function()
test_expression("false")
test_expression("true")
end)
describe("numbers", function()
it("formats integers nicely", function()
test_expression("42")
end)
it("avoids misleading rounding", function()
test_expression("0.3")
assert.equal("0.30000000000000004", dump(0.1 + 0.2))
end)
end)
it("strings", function()
test_expression('"hello world"')
test_expression([["hello \"world\""]])
end)
describe("tables", function()
it("empty", function()
test_expression("{}")
end)
it("lists", function()
test_expression([[
{
false,
true,
"foo",
1,
2,
}]])
end)
it("number keys", function()
test_expression([[
{
[0.5] = false,
[1.5] = true,
[2.5] = "foo",
}]])
end)
it("dicts", function()
test_expression([[{
a = 1,
b = 2,
c = 3,
}]])
end)
it("mixed", function()
test_expression([[{
a = 1,
b = 2,
c = 3,
["d e"] = true,
"foo",
"bar",
}]])
end)
it("nested", function()
test_expression([[{
a = {
1,
{},
},
b = "foo",
c = {
[0.5] = 0.1,
[1.5] = 0.2,
},
}]])
end)
it("circular references", function()
test_expression([[setref(1){
child = {
parent = getref(1),
},
other_child = {
parent = getref(1),
},
}]])
end)
it("supports variable indent", function()
assert.equal('{1,2,3,{foo = "bar",},}', dump({1, 2, 3, {foo = "bar"}}, ""))
assert.equal('{\n "x",\n "y",\n}', dump({"x", "y"}, " "))
end)
end)
end)

View file

@ -93,21 +93,49 @@ describe("serialize", function()
assert_preserves(test_in)
end)
it("strips functions in safe mode", function()
local test_in = {
func = function(a, b)
error("test")
end,
foo = "bar"
}
setfenv(test_in.func, _G)
describe("safe mode", function()
setup(function()
assert(not core.log)
-- logging a deprecation warning will be attempted
function core.log() end
end)
teardown(function()
core.log = nil
end)
it("functions are stripped", function()
local test_in = {
func = function(a, b)
error("test")
end,
foo = "bar"
}
setfenv(test_in.func, _G)
local str = core.serialize(test_in)
assert.not_nil(str:find("loadstring"))
local str = core.serialize(test_in)
assert.not_nil(str:find("loadstring"))
local test_out = core.deserialize(str, true)
assert.is_nil(test_out.func)
assert.equals(test_out.foo, "bar")
local test_out = core.deserialize(str, true)
assert.is_nil(test_out.func)
assert.equals(test_out.foo, "bar")
end)
end)
describe("deprecation warnings", function()
before_each(function()
assert(not core.log)
core.log = spy.new(function(level)
assert(level == "deprecated")
end)
end)
after_each(function()
core.log = nil
end)
it("dumping functions", function()
local t = {f = function() end, g = function() end}
t.t = t
core.serialize(t)
assert.spy(core.log).was.called(1) -- should have been called exactly *once*
end)
end)
it("vectors work", function()

View file

@ -389,7 +389,7 @@ function vector.random_direction()
local x, y, z, l2
repeat -- expected less than two attempts on average (volume sphere vs. cube)
x, y, z = math.random() * 2 - 1, math.random() * 2 - 1, math.random() * 2 - 1
l2 = x*x + y*y + z*z
l2 = x*x + y*y + z*z
until l2 <= 1 and l2 >= 1e-6
-- normalize
local l = math.sqrt(l2)

View file

@ -33,7 +33,7 @@ function core.get_node(pos)
return core.vmanip:get_node_at(pos)
end
function core.get_perlin(seed, octaves, persist, spread)
function core.get_value_noise(seed, octaves, persist, spread)
local params
if type(seed) == "table" then
params = table.copy(seed)
@ -47,12 +47,18 @@ function core.get_perlin(seed, octaves, persist, spread)
}
end
params.seed = core.get_seed(params.seed) -- add mapgen seed
return PerlinNoise(params)
return ValueNoise(params)
end
function core.get_perlin_map(params, size)
function core.get_value_noise_map(params, size)
local params2 = table.copy(params)
params2.seed = core.get_seed(params.seed) -- add mapgen seed
return PerlinNoiseMap(params2, size)
return ValueNoiseMap(params2, size)
end
-- deprecated as of 5.12, as it was not Perlin noise
-- but with no warnings (yet) for compatibility
core.get_perlin = core.get_value_noise
core.get_perlin_map = core.get_value_noise_map
PerlinNoise = ValueNoise
PerlinNoiseMap = ValueNoiseMap

View file

@ -9,8 +9,8 @@ do
all.registered_craftitems = {}
all.registered_tools = {}
for k, v in pairs(all.registered_items) do
-- Disable further modification
setmetatable(v, {__newindex = {}})
-- Ignore new keys
setmetatable(v, {__newindex = function() end})
-- Reassemble the other tables
if v.type == "node" then
getmetatable(v).__index = all.nodedef_default
@ -36,6 +36,9 @@ end
local alias_metatable = {
__index = function(t, name)
return rawget(t, core.registered_aliases[name])
end,
__newindex = function()
error("table is read-only")
end
}
setmetatable(core.registered_items, alias_metatable)

View file

@ -1,20 +1,7 @@
--Luanti
--Copyright (C) 2014 sapier
--Copyright (C) 2023 Gregor Parzefall
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- Copyright (C) 2023 Gregor Parzefall
-- SPDX-License-Identifier: LGPL-2.1-or-later
local BASE_SPACING = 0.1
@ -38,7 +25,7 @@ local function buttonbar_formspec(self)
-- `BASE_SPACING` is used as the minimum spacing, like `gap` in CSS Flexbox.
-- The number of buttons per page is always calculated as if the scroll
-- buttons were visible.
-- buttons were visible.
local avail_space = self.size.x - 2*BASE_SPACING - 2*get_scroll_btn_width()
local btns_per_page = math.floor((avail_space - BASE_SPACING) / (btn_size + BASE_SPACING))

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--this program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function dialog_event_handler(self,event)
if self.user_eventhandler == nil or

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
--------------------------------------------------------------------------------

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
ui = {}
ui.childlist = {}
@ -179,6 +166,10 @@ end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
core.button_handler = function(fields)
if fields["try_quit"] and not fields["key_enter"] then
core.event_handler("MenuQuit")
return
end
if fields["btn_reconnect_yes"] then
gamedata.reconnect_requested = false
gamedata.errormessage = nil

View file

@ -60,6 +60,8 @@ core.register_on_chat_message(function(name, message)
param = param or ""
core.log("verbose", string.format("Handling chat command %q with params %q", cmd, param))
-- Run core.registered_on_chatcommands callbacks.
if core.run_callbacks(core.registered_on_chatcommands, 5, name, cmd, param) then
return true
@ -1275,7 +1277,7 @@ core.register_chatcommand("msg", {
core.log("action", "DM from " .. name .. " to " .. sendto
.. ": " .. message)
core.chat_send_player(sendto, S("DM from @1: @2", name, message))
return true, S("Message sent.")
return true, S("DM sent to @1: @2", sendto, message)
end,
})

View file

@ -61,3 +61,10 @@ function core.register_on_auth_fail(func)
end
end)
end
-- deprecated as of 5.12, as it was not Perlin noise
-- but with no warnings (yet) for compatibility
core.get_perlin = core.get_value_noise
core.get_perlin_map = core.get_value_noise_map
PerlinNoise = ValueNoise
PerlinNoiseMap = ValueNoiseMap

View file

@ -1,5 +1,4 @@
local builtin_shared = ...
local SCALE = 0.667
local facedir_to_euler = {
{y = 0, x = 0, z = 0},
@ -36,9 +35,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
core.register_entity(":__builtin:falling_node", {
initial_properties = {
visual = "item",
visual_size = vector.new(SCALE, SCALE, SCALE),
textures = {},
visual = "node",
physical = true,
is_visible = false,
collide_with_objects = true,
@ -78,43 +75,17 @@ core.register_entity(":__builtin:falling_node", {
self.floats = core.get_item_group(node.name, "float") ~= 0
-- Save liquidtype for falling water
self.liquidtype = def.liquidtype
self.liquidtype = def.liquidtype
-- Set entity visuals
if def.drawtype == "torchlike" or def.drawtype == "signlike" then
local textures
if def.tiles and def.tiles[1] then
local tile = def.tiles[1]
if type(tile) == "table" then
tile = tile.name
end
if def.drawtype == "torchlike" then
textures = { "("..tile..")^[transformFX", tile }
else
textures = { tile, "("..tile..")^[transformFX" }
end
end
local vsize
if def.visual_scale then
local s = def.visual_scale
vsize = vector.new(s, s, s)
end
self.object:set_properties({
is_visible = true,
visual = "upright_sprite",
visual_size = vsize,
textures = textures,
glow = def.light_source,
})
elseif def.drawtype ~= "airlike" then
local itemstring = node.name
if core.is_colored_paramtype(def.paramtype2) then
itemstring = core.itemstring_with_palette(itemstring, node.param2)
end
-- FIXME: solution needed for paramtype2 == "leveled"
-- Set up entity visuals
-- For compatibility with older clients we continue to use "item" visual
-- for simple situations.
local drawtypes = {normal=true, glasslike=true, allfaces=true, nodebox=true}
local p2types = {none=true, facedir=true, ["4dir"]=true}
if drawtypes[def.drawtype] and p2types[def.paramtype2] and def.use_texture_alpha ~= "blend" then
-- Calculate size of falling node
local s = {}
s.x = (def.visual_scale or 1) * SCALE
local s = vector.zero()
s.x = (def.visual_scale or 1) * 0.667
s.y = s.x
s.z = s.x
-- Compensate for wield_scale
@ -125,10 +96,31 @@ core.register_entity(":__builtin:falling_node", {
end
self.object:set_properties({
is_visible = true,
wield_item = itemstring,
visual = "item",
wield_item = node.name,
visual_size = s,
glow = def.light_source,
})
-- Rotate as needed
if def.paramtype2 == "facedir" then
local fdir = node.param2 % 32 % 24
local euler = facedir_to_euler[fdir + 1]
if euler then
self.object:set_rotation(euler)
end
elseif def.paramtype2 == "4dir" then
local fdir = node.param2 % 4
local euler = facedir_to_euler[fdir + 1]
if euler then
self.object:set_rotation(euler)
end
end
elseif def.drawtype ~= "airlike" then
self.object:set_properties({
is_visible = true,
node = node,
glow = def.light_source,
})
end
-- Set collision box (certain nodeboxes only for now)
@ -148,111 +140,6 @@ core.register_entity(":__builtin:falling_node", {
})
end
end
-- Rotate entity
if def.drawtype == "torchlike" then
if (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted")
and node.param2 % 8 == 7 then
self.object:set_yaw(-math.pi*0.25)
else
self.object:set_yaw(math.pi*0.25)
end
elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
and (def.wield_image == "" or def.wield_image == nil))
or def.drawtype == "signlike"
or def.drawtype == "mesh"
or def.drawtype == "normal"
or def.drawtype == "nodebox" then
if (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") then
local fdir = node.param2 % 32 % 24
-- Get rotation from a precalculated lookup table
local euler = facedir_to_euler[fdir + 1]
if euler then
self.object:set_rotation(euler)
end
elseif (def.paramtype2 == "4dir" or def.paramtype2 == "color4dir") then
local fdir = node.param2 % 4
-- Get rotation from a precalculated lookup table
local euler = facedir_to_euler[fdir + 1]
if euler then
self.object:set_rotation(euler)
end
elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and
(def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then
local rot = node.param2 % 8
if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then
-- Change rotation to "floor" by default for non-wallmounted paramtype2
rot = 1
end
local pitch, yaw, roll = 0, 0, 0
if def.drawtype == "nodebox" or def.drawtype == "mesh" then
if rot == 0 then
pitch, yaw = math.pi/2, 0
elseif rot == 1 then
pitch, yaw = -math.pi/2, math.pi
elseif rot == 2 then
pitch, yaw = 0, math.pi/2
elseif rot == 3 then
pitch, yaw = 0, -math.pi/2
elseif rot == 4 then
pitch, yaw = 0, math.pi
elseif rot == 6 then
pitch, yaw = math.pi/2, 0
elseif rot == 7 then
pitch, yaw = -math.pi/2, math.pi
end
else
if rot == 1 then
pitch, yaw = math.pi, math.pi
elseif rot == 2 then
pitch, yaw = math.pi/2, math.pi/2
elseif rot == 3 then
pitch, yaw = math.pi/2, -math.pi/2
elseif rot == 4 then
pitch, yaw = math.pi/2, math.pi
elseif rot == 5 then
pitch, yaw = math.pi/2, 0
elseif rot == 6 then
pitch, yaw = math.pi, -math.pi/2
elseif rot == 7 then
pitch, yaw = 0, -math.pi/2
end
end
if def.drawtype == "signlike" then
pitch = pitch - math.pi/2
if rot == 0 then
yaw = yaw + math.pi/2
elseif rot == 1 then
yaw = yaw - math.pi/2
elseif rot == 6 then
yaw = yaw - math.pi/2
pitch = pitch + math.pi
elseif rot == 7 then
yaw = yaw + math.pi/2
pitch = pitch + math.pi
end
elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then
if rot == 0 or rot == 1 then
roll = roll + math.pi
elseif rot == 6 or rot == 7 then
if def.drawtype ~= "normal" then
roll = roll - math.pi/2
end
else
yaw = yaw + math.pi
end
end
self.object:set_rotation({x=pitch, y=yaw, z=roll})
elseif (def.drawtype == "mesh" and def.paramtype2 == "degrotate") then
local p2 = (node.param2 - (def.place_param2 or 0)) % 240
local yaw = (p2 / 240) * (math.pi * 2)
self.object:set_yaw(yaw)
elseif (def.drawtype == "mesh" and def.paramtype2 == "colordegrotate") then
local p2 = (node.param2 % 32 - (def.place_param2 or 0) % 32) % 24
local yaw = (p2 / 24) * (math.pi * 2)
self.object:set_yaw(yaw)
end
end
end,
get_staticdata = function(self)
@ -308,23 +195,26 @@ core.register_entity(":__builtin:falling_node", {
core.remove_node(bcp)
else
-- We are placing on top so check what's there
np.y = np.y + 1
end
-- Check what's here
local n2 = core.get_node(np)
local nd = core.registered_nodes[n2.name]
-- If it's not air or liquid, remove node and replace it with
-- it's drops
if n2.name ~= "air" and (not nd or nd.liquidtype ~= "source") then
if nd and nd.buildable_to == false then
local n2 = core.get_node(np)
local nd = core.registered_nodes[n2.name]
if not nd or nd.buildable_to then
core.remove_node(np)
else
-- 'walkable' is used to mean "falling nodes can't replace this"
-- here. Normally we would collide with the walkable node itself
-- and place our node on top (so `n2.name == "air"`), but we
-- re-check this in case we ended up inside a node.
if not nd.diggable or nd.walkable then
return false
end
nd.on_dig(np, n2, nil)
-- If it's still there, it might be protected
if core.get_node(np).name == n2.name then
return false
end
else
core.remove_node(np)
end
end

View file

@ -45,6 +45,8 @@ core.features = {
abm_without_neighbors = true,
biome_weights = true,
particle_blend_clip = true,
remove_item_match_meta = true,
httpfetch_additional_methods = true,
}
function core.has_feature(arg)

View file

@ -360,13 +360,12 @@ end
function core.item_drop(itemstack, dropper, pos)
local dropper_is_player = dropper and dropper:is_player()
local p = table.copy(pos)
local cnt = itemstack:get_count()
if dropper_is_player then
p.y = p.y + 1.2
end
local item = itemstack:take_item(cnt)
local obj = core.add_item(p, item)
local obj = core.add_item(p, ItemStack(itemstack))
if obj then
itemstack:clear()
if dropper_is_player then
local dir = dropper:get_look_dir()
dir.x = dir.x * 2.9
@ -375,7 +374,7 @@ function core.item_drop(itemstack, dropper, pos)
obj:set_velocity(dir)
obj:get_luaentity().dropped_by = dropper:get_player_name()
end
return itemstack
return itemstack, obj
end
-- If we reach this, adding the object to the
-- environment failed
@ -514,7 +513,8 @@ function core.node_dig(pos, node, digger)
.. node.name .. " at " .. core.pos_to_string(pos))
local wielded = digger and digger:get_wielded_item()
local drops = core.get_node_drops(node, wielded and wielded:get_name())
local drops = core.get_node_drops(node, wielded and wielded:get_name(),
wielded and ItemStack(wielded), digger, vector.copy(pos))
if wielded then
local wdef = wielded:get_definition()

View file

@ -36,7 +36,7 @@ end
function core.setting_get_pos(name)
return core.settings:get_pos(name)
return core.settings:get_pos(name)
end
@ -112,3 +112,26 @@ if core.set_push_moveresult1 then
end)
core.set_push_moveresult1 = nil
end
-- Protocol version table
-- see also src/network/networkprotocol.cpp
core.protocol_versions = {
["5.0.0"] = 37,
["5.1.0"] = 38,
["5.2.0"] = 39,
["5.3.0"] = 39,
["5.4.0"] = 39,
["5.5.0"] = 40,
["5.6.0"] = 41,
["5.7.0"] = 42,
["5.8.0"] = 43,
["5.9.0"] = 44,
["5.9.1"] = 45,
["5.10.0"] = 46,
["5.11.0"] = 47,
["5.12.0"] = 48,
}
setmetatable(core.protocol_versions, {__newindex = function()
error("core.protocol_versions is read-only")
end})

View file

@ -61,7 +61,7 @@ local function check_modname_prefix(name)
return name:sub(2)
else
-- Enforce that the name starts with the correct mod name.
local expected_prefix = core.get_current_modname() .. ":"
local expected_prefix = (core.get_current_modname() or "") .. ":"
if name:sub(1, #expected_prefix) ~= expected_prefix then
error("Name " .. name .. " does not follow naming conventions: " ..
"\"" .. expected_prefix .. "\" or \":\" prefix required")
@ -95,6 +95,7 @@ function core.register_abm(spec)
check_node_list(spec.nodenames, "nodenames")
check_node_list(spec.neighbors, "neighbors")
assert(type(spec.action) == "function", "Required field 'action' of type function")
core.registered_abms[#core.registered_abms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
@ -128,127 +129,51 @@ function core.register_entity(name, prototype)
prototype.mod_origin = core.get_current_modname() or "??"
end
function core.register_item(name, itemdef)
-- Check name
if name == nil then
error("Unable to register item: Name is nil")
local function preprocess_node(nodedef)
-- Use the nodebox as selection box if it's not set manually
if nodedef.drawtype == "nodebox" and not nodedef.selection_box then
nodedef.selection_box = nodedef.node_box
elseif nodedef.drawtype == "fencelike" and not nodedef.selection_box then
nodedef.selection_box = {
type = "fixed",
fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
}
end
name = check_modname_prefix(tostring(name))
if forbidden_item_names[name] then
error("Unable to register item: Name is forbidden: " .. name)
end
itemdef.name = name
-- Apply defaults and add to registered_* table
if itemdef.type == "node" then
-- Use the nodebox as selection box if it's not set manually
if itemdef.drawtype == "nodebox" and not itemdef.selection_box then
itemdef.selection_box = itemdef.node_box
elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then
itemdef.selection_box = {
type = "fixed",
fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
}
end
if itemdef.light_source and itemdef.light_source > core.LIGHT_MAX then
itemdef.light_source = core.LIGHT_MAX
core.log("warning", "Node 'light_source' value exceeds maximum," ..
" limiting to maximum: " ..name)
end
setmetatable(itemdef, {__index = core.nodedef_default})
core.registered_nodes[itemdef.name] = itemdef
elseif itemdef.type == "craft" then
setmetatable(itemdef, {__index = core.craftitemdef_default})
core.registered_craftitems[itemdef.name] = itemdef
elseif itemdef.type == "tool" then
setmetatable(itemdef, {__index = core.tooldef_default})
core.registered_tools[itemdef.name] = itemdef
elseif itemdef.type == "none" then
setmetatable(itemdef, {__index = core.noneitemdef_default})
else
error("Unable to register item: Type is invalid: " .. dump(itemdef))
if nodedef.light_source and nodedef.light_source > core.LIGHT_MAX then
nodedef.light_source = core.LIGHT_MAX
core.log("warning", "Node 'light_source' value exceeds maximum," ..
" limiting it: " .. nodedef.name)
end
-- Flowing liquid uses param2
if itemdef.type == "node" and itemdef.liquidtype == "flowing" then
itemdef.paramtype2 = "flowingliquid"
if nodedef.liquidtype == "flowing" then
nodedef.paramtype2 = "flowingliquid"
end
end
local function preprocess_craft(itemdef)
-- BEGIN Legacy stuff
if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
core.register_craft({
type="cooking",
output=itemdef.cookresult_itemstring,
recipe=itemdef.name,
cooktime=itemdef.furnace_cooktime
})
end
if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then
core.register_craft({
type="fuel",
recipe=itemdef.name,
burntime=itemdef.furnace_burntime
})
if itemdef.inventory_image == nil and itemdef.image ~= nil then
core.log("deprecated", "The `image` field in craftitem definitions " ..
"is deprecated. Use `inventory_image` instead. " ..
"Craftitem name: " .. itemdef.name, 3)
itemdef.inventory_image = itemdef.image
end
-- END Legacy stuff
itemdef.mod_origin = core.get_current_modname() or "??"
-- Disable all further modifications
getmetatable(itemdef).__newindex = {}
--core.log("Registering item: " .. itemdef.name)
core.registered_items[itemdef.name] = itemdef
core.registered_aliases[itemdef.name] = nil
register_item_raw(itemdef)
end
function core.unregister_item(name)
if not core.registered_items[name] then
core.log("warning", "Not unregistering item " ..name..
" because it doesn't exist.")
return
end
-- Erase from registered_* table
local type = core.registered_items[name].type
if type == "node" then
core.registered_nodes[name] = nil
elseif type == "craft" then
core.registered_craftitems[name] = nil
elseif type == "tool" then
core.registered_tools[name] = nil
end
core.registered_items[name] = nil
unregister_item_raw(name)
end
function core.register_node(name, nodedef)
nodedef.type = "node"
core.register_item(name, nodedef)
end
function core.register_craftitem(name, craftitemdef)
craftitemdef.type = "craft"
-- BEGIN Legacy stuff
if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then
craftitemdef.inventory_image = craftitemdef.image
end
-- END Legacy stuff
core.register_item(name, craftitemdef)
end
function core.register_tool(name, tooldef)
tooldef.type = "tool"
local function preprocess_tool(tooldef)
tooldef.stack_max = 1
-- BEGIN Legacy stuff
if tooldef.inventory_image == nil and tooldef.image ~= nil then
core.log("deprecated", "The `image` field in tool definitions " ..
"is deprecated. Use `inventory_image` instead. " ..
"Tool name: " .. tooldef.name, 3)
tooldef.inventory_image = tooldef.image
end
if tooldef.tool_capabilities == nil and
(tooldef.full_punch_interval ~= nil or
tooldef.basetime ~= nil or
@ -261,6 +186,9 @@ function core.register_tool(name, tooldef)
tooldef.dd_crackiness ~= nil or
tooldef.dd_crumbliness ~= nil or
tooldef.dd_cuttability ~= nil) then
core.log("deprecated", "Specifying tool capabilities directly in the tool " ..
"definition is deprecated. Use the `tool_capabilities` field instead. " ..
"Tool name: " .. tooldef.name, 3)
tooldef.tool_capabilities = {
full_punch_interval = tooldef.full_punch_interval,
basetime = tooldef.basetime,
@ -277,7 +205,7 @@ function core.register_tool(name, tooldef)
end
-- END Legacy stuff
-- This isn't just legacy, but more of a convenience feature
-- Automatically set punch_attack_uses as a convenience feature
local toolcaps = tooldef.tool_capabilities
if toolcaps and toolcaps.punch_attack_uses == nil then
for _, cap in pairs(toolcaps.groupcaps or {}) do
@ -288,8 +216,126 @@ function core.register_tool(name, tooldef)
end
end
end
end
core.register_item(name, tooldef)
local default_tables = {
node = core.nodedef_default,
craft = core.craftitemdef_default,
tool = core.tooldef_default,
none = core.noneitemdef_default,
}
local preprocess_fns = {
node = preprocess_node,
craft = preprocess_craft,
tool = preprocess_tool,
}
function core.register_item(name, itemdef)
-- Check name
if name == nil then
error("Unable to register item: Name is nil")
end
name = check_modname_prefix(tostring(name))
if forbidden_item_names[name] then
error("Unable to register item: Name is forbidden: " .. name)
end
itemdef.name = name
-- Compatibility stuff depending on type
local fn = preprocess_fns[itemdef.type]
if fn then
fn(itemdef)
end
-- Apply defaults
local defaults = default_tables[itemdef.type]
if defaults == nil then
error("Unable to register item: Type is invalid: " .. dump(itemdef))
end
local old_mt = getmetatable(itemdef)
-- TODO most of these checks should become an error after a while (maybe in 2026?)
if old_mt ~= nil and next(old_mt) ~= nil then
-- Note that even registering multiple identical items with the same table
-- is not allowed, due to the 'name' property.
if old_mt.__index == defaults then
core.log("warning", "Item definition table was reused between registrations. "..
"This is unsupported and broken: " .. name)
else
core.log("warning", "Item definition has a metatable, this is "..
"unsupported and it will be overwritten: " .. name)
end
end
setmetatable(itemdef, {__index = defaults})
-- BEGIN Legacy stuff
if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
core.log("deprecated", "The `cookresult_itemstring` item definition " ..
"field is deprecated. Use `core.register_craft` instead. " ..
"Item name: " .. itemdef.name, 2)
core.register_craft({
type="cooking",
output=itemdef.cookresult_itemstring,
recipe=itemdef.name,
cooktime=itemdef.furnace_cooktime
})
end
if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then
core.log("deprecated", "The `furnace_burntime` item definition " ..
"field is deprecated. Use `core.register_craft` instead. " ..
"Item name: " .. itemdef.name, 2)
core.register_craft({
type="fuel",
recipe=itemdef.name,
burntime=itemdef.furnace_burntime
})
end
-- END Legacy stuff
itemdef.mod_origin = core.get_current_modname() or "??"
-- Ignore new keys as a failsafe to prevent mistakes
getmetatable(itemdef).__newindex = function() end
-- Add to registered_* tables
if itemdef.type == "node" then
core.registered_nodes[itemdef.name] = itemdef
elseif itemdef.type == "craft" then
core.registered_craftitems[itemdef.name] = itemdef
elseif itemdef.type == "tool" then
core.registered_tools[itemdef.name] = itemdef
end
core.registered_items[itemdef.name] = itemdef
core.registered_aliases[itemdef.name] = nil
register_item_raw(itemdef)
end
local function make_register_item_wrapper(the_type)
return function(name, itemdef)
itemdef.type = the_type
return core.register_item(name, itemdef)
end
end
core.register_node = make_register_item_wrapper("node")
core.register_craftitem = make_register_item_wrapper("craft")
core.register_tool = make_register_item_wrapper("tool")
function core.unregister_item(name)
if not core.registered_items[name] then
core.log("warning", "Not unregistering item " ..name..
" because it doesn't exist.")
return
end
-- Erase from registered_* table
core.registered_nodes[name] = nil
core.registered_craftitems[name] = nil
core.registered_tools[name] = nil
core.registered_items[name] = nil
unregister_item_raw(name)
end
function core.register_alias(name, convert_to)
@ -300,7 +346,6 @@ function core.register_alias(name, convert_to)
core.log("warning", "Not registering alias, item with same name" ..
" is already defined: " .. name .. " -> " .. convert_to)
else
--core.log("Registering alias: " .. name .. " -> " .. convert_to)
core.registered_aliases[name] = convert_to
register_alias_raw(name, convert_to)
end
@ -315,7 +360,6 @@ function core.register_alias_force(name, convert_to)
core.log("info", "Removed item " ..name..
" while attempting to force add an alias")
end
--core.log("Registering alias: " .. name .. " -> " .. convert_to)
core.registered_aliases[name] = convert_to
register_alias_raw(name, convert_to)
end
@ -406,6 +450,7 @@ core.register_item(":", {
groups = {not_in_creative_inventory=1},
})
local itemdefs_finalized = false
function core.override_item(name, redefinition, del_fields)
if redefinition.name ~= nil then
@ -418,10 +463,16 @@ function core.override_item(name, redefinition, del_fields)
if not item then
error("Attempt to override non-existent item "..name, 2)
end
if itemdefs_finalized then
-- TODO: it's not clear if this needs to be allowed at all?
core.log("warning", "Overriding item " .. name .. " after server startup. " ..
"This is unsupported and can cause problems related to data inconsistency.")
end
for k, v in pairs(redefinition) do
rawset(item, k, v)
end
for _, field in ipairs(del_fields or {}) do
assert(field ~= "name" and field ~= "type")
rawset(item, field, nil)
end
register_item_raw(item)
@ -568,13 +619,57 @@ core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_r
core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration()
core.registered_on_mapblocks_changed, core.register_on_mapblocks_changed = make_registration()
-- A bunch of registrations are read by the C++ side once on env init, so we cannot
-- allow them to change afterwards (see s_env.cpp).
-- Nodes and items do not have this problem but there are obvious consistency
-- problems if this would be allowed.
local function freeze_table(t)
-- Freezing a Lua table is not actually possible without some very intrusive
-- metatable hackery, but we can trivially prevent new additions.
local mt = table.copy(getmetatable(t) or {})
mt.__newindex = function()
error("modification forbidden")
end
setmetatable(t, mt)
end
local function generic_reg_error(what)
return function(something)
local described = what
if type(something) == "table" and type(something.name) == "string" then
described = what .. " " .. something.name
elseif type(something) == "string" then
described = what .. " " .. something
end
error("Tried to register " .. described .. " after load time!")
end
end
core.register_on_mods_loaded(function()
core.after(0, function()
setmetatable(core.registered_on_mapblocks_changed, {
__newindex = function()
error("on_mapblocks_changed callbacks must be registered at load time")
end,
})
itemdefs_finalized = true
-- prevent direct modification
freeze_table(core.registered_abms)
freeze_table(core.registered_lbms)
freeze_table(core.registered_items)
freeze_table(core.registered_nodes)
freeze_table(core.registered_craftitems)
freeze_table(core.registered_tools)
freeze_table(core.registered_aliases)
freeze_table(core.registered_on_mapblocks_changed)
-- neutralize registration functions
core.register_abm = generic_reg_error("ABM")
core.register_lbm = generic_reg_error("LBM")
core.register_item = generic_reg_error("item")
core.unregister_item = function(name)
error("Refusing to unregister item " .. name .. " after load time")
end
core.register_alias = generic_reg_error("alias")
core.register_alias_force = generic_reg_error("alias")
core.register_on_mapblocks_changed = generic_reg_error("on_mapblocks_changed callback")
end)
end)

View file

@ -78,6 +78,8 @@ elseif INIT == "client" then
dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua")
elseif INIT == "emerge" then
dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua")
elseif INIT == "pause_menu" then
dofile(scriptdir .. "pause_menu" .. DIR_DELIM .. "init.lua")
else
error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
end

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
-- Global menu data
menudata = {}
@ -34,7 +21,6 @@ function check_cache_age(key, max_age)
end
function core.on_before_close()
-- called before the menu is closed, either exit or to join a game
cache_settings:write()
end

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2018-24 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2018-24 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
if not core.get_http_api then
return
@ -41,6 +28,7 @@ contentdb = {
REASON_DEPENDENCY = "dependency",
}
-- API documentation: https://content.luanti.org/help/api/
local function get_download_url(package, reason)
local base_url = core.settings:get("contentdb_url")
@ -182,14 +170,16 @@ function contentdb.get_package_by_id(id)
end
function contentdb.calculate_package_id(type, author, name)
local id = author:lower() .. "/"
local function strip_game_suffix(type, name)
if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
id = id .. name:sub(1, #name - 5)
return name:sub(1, #name - 5)
else
id = id .. name
return name
end
return id
end
function contentdb.calculate_package_id(type, author, name)
return author:lower() .. "/" .. strip_game_suffix(type, name)
end
@ -398,7 +388,6 @@ local function fetch_pkgs()
local url = base_url ..
"/api/packages/?type=mod&type=game&type=txp&protocol_version=" ..
core.get_max_supp_proto() .. "&engine_version=" .. core.urlencode(version.string)
for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do
item = item:trim()
if item ~= "" then
@ -406,19 +395,11 @@ local function fetch_pkgs()
end
end
local languages
local current_language = core.get_language()
if current_language ~= "" then
languages = { current_language, "en;q=0.8" }
else
languages = { "en" }
end
local http = core.get_http_api()
local response = http.fetch_sync({
url = url,
extra_headers = {
"Accept-Language: " .. table.concat(languages, ", ")
core.get_http_accept_languages()
},
})
if not response.succeeded then
@ -448,7 +429,7 @@ function contentdb.set_packages_from_api(packages)
-- We currently don't support name changing
local suffix = "/" .. package.name
if alias:sub(-#suffix) == suffix then
contentdb.aliases[alias:lower()] = package.id
contentdb.aliases[strip_game_suffix(packages.type, alias:lower())] = package.id
end
end
end
@ -596,57 +577,54 @@ function contentdb.filter_packages(query, by_type)
end
function contentdb.get_full_package_info(package, callback)
assert(package)
if package.full_info then
callback(package.full_info)
return
end
local function fetch(params)
local version = core.get_version()
local base_url = core.settings:get("contentdb_url")
local languages
local current_language = core.get_language()
if current_language ~= "" then
languages = { current_language, "en;q=0.8" }
else
languages = { "en" }
local function get_package_info(key, path)
return function(package, callback)
assert(package)
if package[key] then
callback(package[key])
return
end
local url = base_url ..
"/api/packages/" .. params.package.url_part .. "/for-client/?" ..
"protocol_version=" .. core.urlencode(core.get_max_supp_proto()) ..
"&engine_version=" .. core.urlencode(version.string) ..
"&formspec_version=" .. core.urlencode(core.get_formspec_version()) ..
"&include_images=false"
local http = core.get_http_api()
local response = http.fetch_sync({
url = url,
extra_headers = {
"Accept-Language: " .. table.concat(languages, ", ")
},
})
if not response.succeeded then
return nil
local function fetch(params)
local version = core.get_version()
local base_url = core.settings:get("contentdb_url")
local url = base_url ..
"/api/packages/" .. params.package.url_part .. params.path .. "?" ..
"protocol_version=" .. core.urlencode(core.get_max_supp_proto()) ..
"&engine_version=" .. core.urlencode(version.string) ..
"&formspec_version=" .. core.urlencode(core.get_formspec_version()) ..
"&include_images=false"
local http = core.get_http_api()
local response = http.fetch_sync({
url = url,
extra_headers = {
core.get_http_accept_languages()
},
})
if not response.succeeded then
return nil
end
return core.parse_json(response.data)
end
return core.parse_json(response.data)
end
local function my_callback(value)
package[key] = value
callback(value)
end
local function my_callback(value)
package.full_info = value
callback(value)
end
if not core.handle_async(fetch, { package = package }, my_callback) then
core.log("error", "ERROR: async event failed")
callback(nil)
if not core.handle_async(fetch, { package = package, path = path }, my_callback) then
core.log("error", "ERROR: async event failed")
callback(nil)
end
end
end
contentdb.get_full_package_info = get_package_info("full_info", "/for-client/")
contentdb.get_package_reviews = get_package_info("reviews", "/for-client/reviews/")
function contentdb.get_formspec_padding()
-- Padding is increased on Android to account for notches
-- TODO: use Android API to determine size of cut outs

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2018-20 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2018-20 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
if not core.get_http_api then
function create_contentdb_dlg()
@ -323,9 +310,17 @@ local function get_formspec(dlgdata)
})
local img_w = cell_h * 3 / 2
-- Use as much of the available space as possible (so no padding on the
-- right/bottom), but don't quite allow the text to touch the border.
local text_w = cell_w - img_w - 0.25 - 0.025
local text_h = cell_h - 0.25 - 0.025
local start_idx = (cur_page - 1) * num_per_page + 1
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i]
local text = core.colorize(mt_color_green, package.title) ..
core.colorize("#BFBFBF", " by " .. package.author) .. "\n" ..
package.short_description
table.insert_all(formspec, {
"container[",
@ -340,13 +335,14 @@ local function get_formspec(dlgdata)
"image[0,0;", img_w, ",", cell_h, ";",
core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]",
"label[", img_w + 0.25 + 0.05, ",0.5;",
core.formspec_escape(
core.colorize(mt_color_green, package.title) ..
core.colorize("#BFBFBF", " by " .. package.author)), "]",
"label[", img_w + 0.25, ",0.25;", text_w, ",", text_h, ";",
core.formspec_escape(text), "]",
"textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;",
core.formspec_escape(package.short_description), "]",
-- Add a tooltip in case the label overflows and the short description is cut off.
"tooltip[", img_w + 0.25, ",0.25;", text_w, ",", text_h, ";",
-- Text in tooltips doesn't wrap automatically, so we do it manually to
-- avoid everything being one long line.
core.formspec_escape(core.wrap_text(package.short_description, 80)), "]",
"style[view_", i, ";border=false]",
"style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]",
@ -362,7 +358,7 @@ local function get_formspec(dlgdata)
end
table.insert_all(formspec, {
"container[", cell_w - 0.625,",", 0.25, "]",
"container[", cell_w - 0.625,",", 0.125, "]",
})
if package.downloading then

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2018-24 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2018-24 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function is_still_visible(dlg)
local this = ui.find_by_name("install_dialog")

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2018-24 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2018-24 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
function get_formspec(data)
local package = data.package

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2018-24 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2018-24 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function get_info_formspec(size, padding, text)
@ -32,6 +19,7 @@ end
local function get_formspec(data)
local package = data.package
local window_padding = contentdb.get_formspec_padding()
local size = contentdb.get_formspec_size()
size.x = math.min(size.x, 20)
@ -42,7 +30,7 @@ local function get_formspec(data)
if not data.loading and not data.loading_error then
data.loading = true
contentdb.get_full_package_info(data.package, function(info)
contentdb.get_full_package_info(package, function(info)
data.loading = false
if info == nil then
@ -61,7 +49,7 @@ local function get_formspec(data)
-- check to see if that happened
if not data.info then
if data.loading_error then
return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
return get_info_formspec(size, window_padding, fgettext("Error loading package information"))
end
return get_info_formspec(size, window_padding, fgettext("Loading..."))
end
@ -103,15 +91,15 @@ local function get_formspec(data)
local left_button_rect = "0,0;2.875,1"
local right_button_rect = "3.125,0;2.875,1"
if data.package.downloading then
if package.downloading then
formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;"
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif data.package.queued then
elseif package.queued then
formspec[#formspec + 1] = "style[queued;border=false]"
formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
elseif not data.package.path then
elseif not package.path then
formspec[#formspec + 1] = "style[install;bgcolor=green]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = right_button_rect
@ -119,7 +107,7 @@ local function get_formspec(data)
formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size)
formspec[#formspec + 1] = "]"
else
if data.package.installed_release < data.package.release then
if package.installed_release < package.release then
-- The install_ action also handles updating
formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]"
formspec[#formspec + 1] = "button["
@ -137,10 +125,12 @@ local function get_formspec(data)
formspec[#formspec + 1] = "]"
end
local review_count = info.reviews.positive + info.reviews.neutral + info.reviews.negative
local current_tab = data.current_tab or 1
local tab_titles = {
fgettext("Description"),
fgettext("Information"),
fgettext("Reviews") .. core.formspec_escape(" [" .. review_count .. "]"),
}
local tab_body_height = bottom_buttons_y - 2.8
@ -162,8 +152,8 @@ local function get_formspec(data)
local winfo = core.get_window_info()
local fs_to_px = winfo.size.x / winfo.max_formspec_size.x
for i, ss in ipairs(info.screenshots) do
local path = get_screenshot(data.package, ss.url, 2)
hypertext = hypertext .. "<action name=\"ss_" .. i .. "\"><img name=\"" ..
local path = get_screenshot(package, ss.url, 2)
hypertext = hypertext .. "<action name=\"ss_".. i .. "\"><img name=\"" ..
core.hypertext_escape(path) .. "\" width=" .. (3 * fs_to_px) ..
" height=" .. (2 * fs_to_px) .. "></action>"
if i ~= #info.screenshots then
@ -194,22 +184,54 @@ local function get_formspec(data)
hypertext = hypertext .. "\n\n" .. info.long_description.body
-- Fix the path to blank.png. This is needed for bullet indentation.
hypertext = hypertext:gsub("<img name=\"?blank.png\"? ",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "blank.png\" ")
table.insert_all(formspec, {
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
";desc;", core.formspec_escape(hypertext), "]",
})
elseif current_tab == 2 then
local hypertext = info.info_hypertext.head .. info.info_hypertext.body
table.insert_all(formspec, {
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
";info;", core.formspec_escape(hypertext), "]",
})
elseif current_tab == 3 then
if not package.reviews and not data.reviews_error and not data.reviews_loading then
data.reviews_loading = true
contentdb.get_package_reviews(package, function(reviews)
if not reviews then
data.reviews_error = true
end
ui.update()
end)
end
if package.reviews then
local hypertext = package.reviews.head .. package.reviews.body
-- Provide correct path to blank.png image. This is needed for bullet indentation.
hypertext = hypertext:gsub("<img name=\"?blank.png\"? ",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "blank.png\" ")
-- Placeholders in reviews hypertext for icons
hypertext = hypertext:gsub("<thumbsup>",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "contentdb_thumb_up.png\" width=24>")
hypertext = hypertext:gsub("<thumbsdown>",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "contentdb_thumb_down.png\" width=24>")
hypertext = hypertext:gsub("<neutral>",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "contentdb_neutral.png\" width=24>")
table.insert_all(formspec, {
"hypertext[0,0;", W, ",", tab_body_height - 0.375,
";reviews;", core.formspec_escape(hypertext), "]",
})
elseif data.reviews_error then
table.insert_all(formspec, {"label[2,2;", fgettext("Error loading reviews"), "]"} )
else
table.insert_all(formspec, {"label[2,2;", fgettext("Loading..."), "]"} )
end
else
error("Unknown tab " .. current_tab)
end
@ -269,9 +291,10 @@ local function handle_submit(this, fields)
end
if fields.open_contentdb then
local url = ("%s/packages/%s/?protocol_version=%d"):format(
core.settings:get("contentdb_url"), package.url_part,
core.get_max_supp_proto())
local version = core.get_version()
local url = core.settings:get("contentdb_url") .. "/packages/" .. package.url_part ..
"/?protocol_version=" .. core.urlencode(core.get_max_supp_proto()) ..
"&engine_version=" .. core.urlencode(version.string)
core.open_url(url)
return true
end
@ -295,7 +318,8 @@ local function handle_submit(this, fields)
end
if handle_hypertext_event(this, fields.desc, info.long_description) or
handle_hypertext_event(this, fields.info, info.info_hypertext) then
handle_hypertext_event(this, fields.info, info.info_hypertext) or
(package.reviews and handle_hypertext_event(this, fields.reviews, package.reviews)) then
return true
end
end

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2023 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2023 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local path = core.get_mainmenu_path() .. DIR_DELIM .. "content"

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2013 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
--------------------------------------------------------------------------------
local function get_last_folder(text,count)

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2023-24 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2023-24 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
-- Screenshot

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2022 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2022 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
local mods_dir = "/tmp/.minetest/mods"
local games_dir = "/tmp/.minetest/games"

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2023 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2023 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
update_detector = {}

View file

@ -1,5 +1,5 @@
{
"#": "https://github.com/orgs/minetest/teams/engine/members",
"#": "https://github.com/orgs/luanti-org/teams/engine/members",
"core_developers": [
"Perttu Ahola (celeron55) <celeron55@gmail.com> [Project founder]",
"sfan5 <sfan5@live.de>",
@ -15,7 +15,8 @@
"Gregor Parzefall (grorp)",
"Lars Müller (luatic)",
"cx384",
"sfence"
"sfence",
"y5nw"
],
"previous_core_developers": [
"BlockMen",
@ -38,7 +39,7 @@
"Hugues Ross <hugues.ross@gmail.com>",
"Dmitry Kostenko (x2048) <codeforsmile@gmail.com>"
],
"#": "Currently only https://github.com/orgs/minetest/teams/triagers/members",
"#": "Currently only https://github.com/orgs/luanti-org/teams/triagers/members",
"core_team": [
"Zughy [Issue triager]",
"wsor [Issue triager]",
@ -46,25 +47,20 @@
],
"#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py",
"contributors": [
"JosiahWI",
"1F616EMO",
"y5nw",
"Erich Schubert",
"numzero",
"red-001 <red-001@outlook.ie>",
"David Heidelberg",
"Wuzzy",
"paradust7",
"HybridDog",
"Zemtzov7",
"kromka-chleba",
"AFCMS",
"chmodsayshello",
"OgelGames"
"wrrrzr",
"siliconsniffer",
"JosiahWI",
"veprogames",
"Miguel P.L",
"AFCMS"
],
"previous_contributors": [
"Ælla Chiana Moskopp (erle) <erle@dieweltistgarnichtso.net> [Logo]",
"numzero",
"red-001 <red-001@outlook.ie>",
"Giuseppe Bilotta",
"HybridDog",
"ClobberXD",
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>",
@ -77,6 +73,7 @@
"stujones11",
"Rogier <rogier777@gmail.com>",
"Gregory Currie (gregorycu)",
"paradust7",
"JacobF",
"Jeija <jeija@mesecons.net>"
]

View file

@ -21,7 +21,7 @@ local function clients_list_formspec(dialogdata)
"size[6,9.5]",
TOUCH_GUI and "padding[0.01,0.01]" or "",
"hypertext[0,0;6,1.5;;<global margin=5 halign=center valign=middle>",
fgettext("This is the list of clients connected to\n$1",
fgettext("Players connected to\n$1",
"<b>" .. core.hypertext_escape(servername) .. "</b>") .. "]",
"textlist[0.5,1.5;5,6.8;;" .. fmt_formspec_list(clients_list) .. "]",
"button[1.5,8.5;3,0.8;quit;OK]"
@ -32,7 +32,7 @@ end
local function clients_list_buttonhandler(this, fields)
if fields.quit then
this:delete()
this:delete()
return true
end
return false

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2013 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
--------------------------------------------------------------------------------
@ -299,7 +286,7 @@ local function handle_buttons(this, fields)
worldfile:set("load_mod_" .. mod.name, mod.virtual_path)
was_set[mod.name] = true
elseif not was_set[mod.name] then
worldfile:set("load_mod_" .. mod.name, "false")
worldfile:remove("load_mod_" .. mod.name)
end
elseif mod.enabled then
gamedata.errormessage = fgettext_ne("Failed to enable mo" ..

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function table_to_flags(ftable)
-- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
--------------------------------------------------------------------------------

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function delete_world_formspec(dialogdata)

View file

@ -0,0 +1,108 @@
-- Luanti
-- SPDX-License-Identifier: LGPL-2.1-or-later
-- Modified based on dlg_reinstall_mtg.lua
-- Note that this is only needed for migrating from <5.11 to 5.12.
local doc_url = "https://docs.luanti.org/for-players/controls/"
local SETTING_NAME = "no_keycode_migration_warning"
local function get_formspec(dialogdata)
local markup = table.concat({
"<big>" .. hgettext("Keybindings changed") .. "</big>",
hgettext("The input handling system was reworked in Luanti 5.12.0."),
hgettext("As a result, your keybindings may have been changed."),
hgettext("Check out the key settings or refer to the documentation:"),
("<action name='doc_url'><style color='cyan' hovercolor='orangered'>%s</style></action>"):format(doc_url),
}, "\n")
return table.concat({
"formspec_version[6]",
"size[12,7]",
"hypertext[0.5,0.5;11,4.7;text;", core.formspec_escape(markup), "]",
"container[0.5,5.7]",
"button[0,0;4,0.8;dismiss;", fgettext("Close"), "]",
"button[4.5,0;6.5,0.8;reconfigure;", fgettext("Open settings"), "]",
"container_end[]",
})
end
local function close_dialog(this)
cache_settings:set_bool(SETTING_NAME, true)
this:delete()
end
local function buttonhandler(this, fields)
if fields.reconfigure then
close_dialog(this)
local maintab = ui.find_by_name("maintab")
local dlg = create_settings_dlg("controls_keyboard_and_mouse")
dlg:set_parent(maintab)
maintab:hide()
dlg:show()
return true
end
if fields.dismiss then
close_dialog(this)
return true
end
if fields.text == "action:doc_url" then
core.open_url(doc_url)
end
end
local function eventhandler(event)
if event == "DialogShow" then
mm_game_theme.set_engine()
return true
elseif event == "MenuQuit" then
-- Don't allow closing the dialog with ESC, but still allow exiting
-- Luanti
core.close()
return true
end
return false
end
local function create_rebind_keys_dlg()
local dlg = dialog_create("dlg_rebind_keys", get_formspec,
buttonhandler, eventhandler)
return dlg
end
function migrate_keybindings()
-- Show migration dialog if the user upgraded from an earlier version
-- and this has not yet been shown before, *or* if keys settings had to be changed
if core.is_first_run then
cache_settings:set_bool(SETTING_NAME, true)
end
local has_migration = not cache_settings:get_bool(SETTING_NAME)
-- normalize all existing key settings, this converts them from KEY_KEY_C to SYSTEM_SCANCODE_6
local settings = core.settings:to_table()
for name, value in pairs(settings) do
if name:match("^keymap_") then
local normalized = core.normalize_keycode(value)
if value ~= normalized then
has_migration = true
core.settings:set(name, normalized)
end
end
end
if not has_migration then
return
end
local maintab = ui.find_by_name("maintab")
local dlg = create_rebind_keys_dlg()
dlg:set_parent(maintab)
maintab:hide()
dlg:show()
ui.update()
end

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2022 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2022 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
--------------------------------------------------------------------------------

View file

@ -1,25 +1,12 @@
--Luanti
--Copyright (C) 2023 Gregor Parzefall
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2023 Gregor Parzefall
-- SPDX-License-Identifier: LGPL-2.1-or-later
---- IMPORTANT ----
-- This whole file can be removed after a while.
-- It was only directly useful for upgrades from 5.7.0 to 5.8.0, but
-- maybe some odd fellow directly upgrades from 5.6.1 to 5.9.0 in the future...
-- see <https://github.com/minetest/minetest/pull/13850> in case it's not obvious
-- see <https://github.com/luanti-org/luanti/pull/13850> in case it's not obvious
---- ----
local SETTING_NAME = "no_mtg_notification"
@ -67,10 +54,10 @@ end
local function get_formspec(dialogdata)
local markup = table.concat({
"<big>", fgettext("Minetest Game is no longer installed by default"), "</big>\n",
fgettext("For a long time, Luanti shipped with a default game called \"Minetest Game\". " ..
"<big>", hgettext("Minetest Game is no longer installed by default"), "</big>\n",
hgettext("For a long time, Luanti shipped with a default game called \"Minetest Game\". " ..
"Since version 5.8.0, Luanti ships without a default game."), "\n",
fgettext("If you want to continue playing in your Minetest Game worlds, you need to reinstall Minetest Game."),
hgettext("If you want to continue playing in your Minetest Game worlds, you need to reinstall Minetest Game."),
})
return table.concat({

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
--------------------------------------------------------------------------------

View file

@ -0,0 +1,105 @@
-- Luanti
-- Copyright (C) 2024 cx384
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function get_formspec(dialogdata)
local TOUCH_GUI = core.settings:get_bool("touch_gui")
local server = dialogdata.server
local group_by_prefix = dialogdata.group_by_prefix
local expand_all = dialogdata.expand_all
-- A wrongly behaving server may send ill formed mod names
table.sort(server.mods)
local cells = {}
if group_by_prefix then
local function get_prefix(mod)
return mod:match("[^_]*")
end
local count = {}
for _, mod in ipairs(server.mods) do
local prefix = get_prefix(mod)
count[prefix] = (count[prefix] or 0) + 1
end
local last_prefix
local function add_row(depth, mod)
table.insert(cells, ("%d"):format(depth))
table.insert(cells, mod)
end
for i, mod in ipairs(server.mods) do
local prefix = get_prefix(mod)
if last_prefix == prefix then
add_row(1, mod)
elseif count[prefix] > 1 then
add_row(0, prefix)
add_row(1, mod)
else
add_row(0, mod)
end
last_prefix = prefix
end
else
cells = table.copy(server.mods)
end
for i, cell in ipairs(cells) do
cells[i] = core.formspec_escape(cell)
end
cells = table.concat(cells, ",")
local heading
if server.gameid then
heading = fgettext("The $1 server uses a game called $2 and the following mods:",
"<b>" .. core.hypertext_escape(server.name) .. "</b>",
"<style font=mono>" .. core.hypertext_escape(server.gameid) .. "</style>")
else
heading = fgettext("The $1 server uses the following mods:",
"<b>" .. core.hypertext_escape(server.name) .. "</b>")
end
local formspec = {
"formspec_version[8]",
"size[8,9.5]",
TOUCH_GUI and "padding[0.01,0.01]" or "",
"hypertext[0,0;8,1.5;;<global margin=5 halign=center valign=middle>", heading, "]",
"tablecolumns[", group_by_prefix and
(expand_all and "indent;text" or "tree;text") or "text", "]",
"table[0.5,1.5;7,6.8;mods;", cells, "]",
"checkbox[0.5,8.7;group_by_prefix;", fgettext("Group by prefix"), ";",
group_by_prefix and "true" or "false", "]",
group_by_prefix and ("checkbox[0.5,9.15;expand_all;" .. fgettext("Expand all") .. ";" ..
(expand_all and "true" or "false") .. "]") or "",
"button[5.5,8.5;2,0.8;quit;OK]"
}
return table.concat(formspec, "")
end
local function buttonhandler(this, fields)
if fields.quit then
this:delete()
return true
end
if fields.group_by_prefix then
this.data.group_by_prefix = core.is_yes(fields.group_by_prefix)
return true
end
if fields.expand_all then
this.data.expand_all = core.is_yes(fields.expand_all)
return true
end
return false
end
function create_server_list_mods_dialog(server)
local retval = dialog_create("dlg_server_list_mods",
get_formspec,
buttonhandler,
nil)
retval.data.group_by_prefix = false
retval.data.expand_all = false
retval.data.server = server
return retval
end

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2013 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
mm_game_theme = {}

View file

@ -1,27 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
mt_color_grey = "#AAAAAA"
mt_color_blue = "#6389FF"
mt_color_lightblue = "#99CCFF"
mt_color_green = "#72FF63"
mt_color_dark_green = "#25C191"
mt_color_orange = "#FF8800"
mt_color_red = "#FF3300"
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
MAIN_TAB_W = 15.5
MAIN_TAB_H = 7.1
@ -35,6 +14,7 @@ local basepath = core.get_builtin_path()
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM
dofile(basepath .. "common" .. DIR_DELIM .. "menu.lua")
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
@ -47,7 +27,7 @@ dofile(menupath .. DIR_DELIM .. "game_theme.lua")
dofile(menupath .. DIR_DELIM .. "content" .. DIR_DELIM .. "init.lua")
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
dofile(menupath .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.lua")
dofile(basepath .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.lua")
dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua")
dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua")
@ -55,7 +35,9 @@ dofile(menupath .. DIR_DELIM .. "dlg_register.lua")
dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua")
dofile(menupath .. DIR_DELIM .. "dlg_reinstall_mtg.lua")
dofile(menupath .. DIR_DELIM .. "dlg_rebind_keys.lua")
dofile(menupath .. DIR_DELIM .. "dlg_clients_list.lua")
dofile(menupath .. DIR_DELIM .. "dlg_server_list_mods.lua")
local tabs = {
content = dofile(menupath .. DIR_DELIM .. "tab_content.lua"),
@ -131,6 +113,7 @@ local function init_globals()
ui.update()
check_reinstall_mtg()
migrate_keybindings()
check_new_version()
end

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2020 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2020 rubenwardy
-- SPDX-License-Identifier: LGPL-2.1-or-later
serverlistmgr = {
-- continent code we detected for ourselves

View file

@ -1,28 +0,0 @@
--Luanti
--Copyright (C) 2022 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local path = core.get_mainmenu_path() .. DIR_DELIM .. "settings"
dofile(path .. DIR_DELIM .. "settingtypes.lua")
dofile(path .. DIR_DELIM .. "dlg_change_mapgen_flags.lua")
dofile(path .. DIR_DELIM .. "dlg_settings.lua")
-- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'.
-- For RUN_IN_PLACE the generated files may appear in the 'bin' folder.
-- See comment and alternative line at the end of 'generate_from_settingtypes.lua'.
-- dofile(path .. DIR_DELIM .. "generate_from_settingtypes.lua")

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2013 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function prepare_credits(dest, source)

View file

@ -1,20 +1,7 @@
--Luanti
--Copyright (C) 2014 sapier
--Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function get_content_icons(packages_with_updates)

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
local current_game, singleplayer_refresh_gamebar

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2014 sapier
-- SPDX-License-Identifier: LGPL-2.1-or-later
local function get_sorted_servers()
local servers = {
@ -129,6 +116,7 @@ local function get_formspec(tabview, name, tabdata)
local retval =
-- Search
"field[0.25,0.25;7,0.75;te_search;;" .. core.formspec_escape(tabdata.search_for) .. "]" ..
"tooltip[te_search;" .. fgettext("Possible filters\ngame:<name>\nmod:<name>\nplayer:<name>") .. "]" ..
"field_enter_after_edit[te_search;true]" ..
"container[7.25,0.25]" ..
"image_button[0,0;0.75,0.75;" .. core.formspec_escape(defaulttexturedir .. "search.png") .. ";btn_mp_search;]" ..
@ -177,6 +165,26 @@ local function get_formspec(tabview, name, tabdata)
core.formspec_escape(gamedata.serverdescription) .. "]"
end
-- Mods button
local mods = selected_server.mods
if mods and #mods > 0 then
local tooltip = ""
if selected_server.gameid then
tooltip = fgettext("Game: $1", selected_server.gameid) .. "\n"
end
tooltip = tooltip .. fgettext("Number of mods: $1", #mods)
retval = retval ..
"tooltip[btn_view_mods;" .. tooltip .. "]" ..
"style[btn_view_mods;padding=6]" ..
"image_button[4,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir ..
"server_view_mods.png") .. ";btn_view_mods;]"
else
retval = retval .. "image[4.1,1.4;0.3,0.3;" .. core.formspec_escape(defaulttexturedir ..
"server_view_mods_unavailable.png") .. "]"
end
-- Clients list button
local clients_list = selected_server.clients_list
local can_view_clients_list = clients_list and #clients_list > 0
if can_view_clients_list then
@ -186,23 +194,31 @@ local function get_formspec(tabview, name, tabdata)
local max_clients = 5
if #clients_list > max_clients then
retval = retval .. "tooltip[btn_view_clients;" ..
fgettext("Clients:\n$1", table.concat(clients_list, "\n", 1, max_clients)) .. "\n..." .. "]"
fgettext("Players:\n$1", table.concat(clients_list, "\n", 1, max_clients)) .. "\n..." .. "]"
else
retval = retval .. "tooltip[btn_view_clients;" ..
fgettext("Clients:\n$1", table.concat(clients_list, "\n")) .. "]"
fgettext("Players:\n$1", table.concat(clients_list, "\n")) .. "]"
end
retval = retval .. "style[btn_view_clients;padding=6]"
retval = retval .. "image_button[4.5,1.3;0.5,0.5;" .. core.formspec_escape(defaulttexturedir ..
"server_view_clients.png") .. ";btn_view_clients;]"
else
retval = retval .. "image[4.6,1.4;0.3,0.3;" .. core.formspec_escape(defaulttexturedir ..
"server_view_clients_unavailable.png") .. "]"
end
-- URL button
if selected_server.url then
retval = retval .. "tooltip[btn_server_url;" .. fgettext("Open server website") .. "]"
retval = retval .. "style[btn_server_url;padding=6]"
retval = retval .. "image_button[" .. (can_view_clients_list and "4" or "4.5") .. ",1.3;0.5,0.5;" ..
retval = retval .. "image_button[3.5,1.3;0.5,0.5;" ..
core.formspec_escape(defaulttexturedir .. "server_url.png") .. ";btn_server_url;]"
else
retval = retval .. "image[3.6,1.4;0.3,0.3;" .. core.formspec_escape(defaulttexturedir ..
"server_url_unavailable.png") .. "]"
end
-- Favorites toggle button
if is_selected_fav() then
retval = retval .. "tooltip[btn_delete_favorite;" .. fgettext("Remove favorite") .. "]"
retval = retval .. "style[btn_delete_favorite;padding=6]"
@ -289,19 +305,109 @@ end
--------------------------------------------------------------------------------
local function search_server_list(input)
local function parse_search_input(input)
if not input:find("%S") then
return -- Return nil if nothing to search for
end
-- Search is not case sensitive
input = input:lower()
local query = {keywords = {}, mods = {}, players = {}}
-- Process quotation enclosed parts
input = input:gsub('(%S?)"([^"]*)"(%S?)', function(before, match, after)
if before == "" and after == "" then -- Also have be separated by spaces
table.insert(query.keywords, match)
return " "
end
return before..'"'..match..'"'..after
end)
-- Separate by space characters and handle special prefixes
-- (words with special prefixes need an exact match and none of them can contain spaces)
for word in input:gmatch("%S+") do
local mod = word:match("^mod:(.*)")
table.insert(query.mods, mod)
local player = word:match("^player:(.*)")
table.insert(query.players, player)
local game = word:match("^game:(.*)")
query.game = query.game or game
if not (mod or player or game) then
table.insert(query.keywords, word)
end
end
return query
end
-- Prepares the server to be used for searching
local function uncapitalize_server(server)
local function table_lower(t)
local r = {}
for i, s in ipairs(t or {}) do
r[i] = s:lower()
end
return r
end
return {
name = (server.name or ""):lower(),
description = (server.description or ""):lower(),
gameid = (server.gameid or ""):lower(),
mods = table_lower(server.mods),
clients_list = table_lower(server.clients_list),
}
end
-- Returns false if the query does not match
-- otherwise returns a number to adjust the sorting priority
local function matches_query(server, query)
-- Search is not case sensitive
server = uncapitalize_server(server)
-- Check if mods found
for _, mod in ipairs(query.mods) do
if table.indexof(server.mods, mod) < 0 then
return false
end
end
-- Check if players found
for _, player in ipairs(query.players) do
if table.indexof(server.clients_list, player) < 0 then
return false
end
end
-- Check if game matches
if query.game and query.game ~= server.gameid then
return false
end
-- Check if keyword found
local name_matches = true
local description_matches = true
for _, keyword in ipairs(query.keywords) do
name_matches = name_matches and server.name:find(keyword, 1, true)
description_matches = description_matches and server.description:find(keyword, 1, true)
end
return name_matches and 50 or description_matches and 0
end
local function search_server_list(input, tabdata)
menudata.search_result = nil
if #serverlistmgr.servers < 2 then
return
end
-- setup the keyword list
local keywords = {}
for word in input:gmatch("%S+") do
table.insert(keywords, word:lower())
end
if #keywords == 0 then
tabdata.pre_search_selection = tabdata.pre_search_selection or find_selected_server()
-- setup the search query
local query = parse_search_input(input)
if not query then
return
end
@ -310,16 +416,9 @@ local function search_server_list(input)
-- Search the serverlist
local search_result = {}
for i, server in ipairs(serverlistmgr.servers) do
local name_matches, description_matches = true, true
for _, keyword in ipairs(keywords) do
name_matches = name_matches and not not
(server.name or ""):lower():find(keyword, 1, true)
description_matches = description_matches and not not
(server.description or ""):lower():find(keyword, 1, true)
end
if name_matches or description_matches then
server.points = #serverlistmgr.servers - i
+ (name_matches and 50 or 0)
local match = matches_query(server, query)
if match then
server.points = #serverlistmgr.servers - i + match
table.insert(search_result, server)
end
end
@ -328,10 +427,32 @@ local function search_server_list(input)
return
end
local current_server = find_selected_server()
table.sort(search_result, function(a, b)
return a.points > b.points
end)
menudata.search_result = search_result
-- Keep current selection if it's in search results
if current_server then
for _, server in ipairs(search_result) do
if server.address == current_server.address and
server.port == current_server.port then
return
end
end
end
-- Find first compatible server (favorite or public)
for _, server in ipairs(search_result) do
if is_server_protocol_compat(server.proto_min, server.proto_max) then
set_selected_server(server)
return
end
end
-- If no compatible server found, clear selection
set_selected_server(nil)
end
local function main_button_handler(tabview, fields, name, tabdata)
@ -371,6 +492,7 @@ local function main_button_handler(tabview, fields, name, tabdata)
end
if event.type == "CHG" then
set_selected_server(server)
tabdata.pre_search_selection = nil
return true
end
end
@ -384,11 +506,9 @@ local function main_button_handler(tabview, fields, name, tabdata)
if fields.btn_delete_favorite then
local idx = core.get_table_index("servers")
if not idx then return end
local server = tabdata.lookup[idx]
if not server then return end
serverlistmgr.delete_favorite(server)
set_selected_server(server)
serverlistmgr.delete_favorite(tabdata.lookup[idx])
set_selected_server(tabdata.lookup[idx+1])
return true
end
@ -405,20 +525,27 @@ local function main_button_handler(tabview, fields, name, tabdata)
return true
end
if fields.btn_view_mods then
local dlg = create_server_list_mods_dialog(find_selected_server())
dlg:set_parent(tabview)
tabview:hide()
dlg:show()
return true
end
if fields.btn_mp_clear then
tabdata.search_for = ""
menudata.search_result = nil
if tabdata.pre_search_selection then
set_selected_server(tabdata.pre_search_selection)
tabdata.pre_search_selection = nil
end
return true
end
if fields.btn_mp_search or fields.key_enter_field == "te_search" then
tabdata.search_for = fields.te_search
search_server_list(fields.te_search:lower())
if menudata.search_result then
-- Note: This clears the selection if there are no results
set_selected_server(menudata.search_result[1])
end
search_server_list(fields.te_search, tabdata)
return true
end

View file

@ -0,0 +1,13 @@
local scriptpath = core.get_builtin_path()
local pausepath = scriptpath.."pause_menu"..DIR_DELIM
local commonpath = scriptpath.."common"..DIR_DELIM
-- we're in-game, so no absolute paths are needed
defaulttexturedir = ""
local builtin_shared = {}
assert(loadfile(commonpath .. "register.lua"))(builtin_shared)
assert(loadfile(commonpath .. "menu.lua"))(builtin_shared)
assert(loadfile(pausepath .. "register.lua"))(builtin_shared)
dofile(commonpath .. "settings" .. DIR_DELIM .. "init.lua")

View file

@ -0,0 +1,5 @@
local builtin_shared = ...
local make_registration = builtin_shared.make_registration
core.registered_on_formspec_input, core.register_on_formspec_input = make_registration()

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2016 T4im
-- SPDX-License-Identifier: LGPL-2.1-or-later
local S = core.get_translator("__builtin")

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2016 T4im
-- SPDX-License-Identifier: LGPL-2.1-or-later
local format, pairs, type = string.format, pairs, type
local core, get_current_modname = core, core.get_current_modname

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2016 T4im
-- SPDX-License-Identifier: LGPL-2.1-or-later
local S = core.get_translator("__builtin")
-- Note: In this file, only messages are translated

View file

@ -1,19 +1,6 @@
--Luanti
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-- Luanti
-- Copyright (C) 2016 T4im
-- SPDX-License-Identifier: LGPL-2.1-or-later
local setmetatable = setmetatable
local pairs, format = pairs, string.format
local min, max, huge = math.min, math.max, math.huge

File diff suppressed because it is too large Load diff