mirror of
https://github.com/luanti-org/luanti.git
synced 2025-09-15 18:57:08 +00:00
In-game settings menu using separate Lua environment (#15614)
This commit is contained in:
parent
3cb07d5fb6
commit
eeb6cab4c4
48 changed files with 652 additions and 290 deletions
|
@ -47,7 +47,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")
|
||||
|
|
|
@ -1,435 +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 make = {}
|
||||
|
||||
|
||||
-- This file defines various component constructors, of the form:
|
||||
--
|
||||
-- make.component(setting)
|
||||
--
|
||||
-- `setting` is a table representing the settingtype.
|
||||
--
|
||||
-- A component is a table with the following:
|
||||
--
|
||||
-- * `full_width`: (Optional) true if the component shouldn't reserve space for info / reset.
|
||||
-- * `info_text`: (Optional) string, informational text shown in an info icon.
|
||||
-- * `setting`: (Optional) the setting.
|
||||
-- * `max_w`: (Optional) maximum width, `avail_w` will never exceed this.
|
||||
-- * `resettable`: (Optional) if this is true, a reset button is shown.
|
||||
-- * `get_formspec = function(self, avail_w)`:
|
||||
-- * `avail_w` is the available width for the component.
|
||||
-- * Returns `fs, used_height`.
|
||||
-- * `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`.
|
||||
-- * `on_submit = function(self, fields, parent)`:
|
||||
-- * `fields`: submitted formspec fields
|
||||
-- * `parent`: the fstk element for the settings UI, use to show dialogs
|
||||
-- * Return true if the event was handled, to prevent future components receiving it.
|
||||
|
||||
|
||||
local function get_label(setting)
|
||||
local show_technical_names = core.settings:get_bool("show_technical_names")
|
||||
if not show_technical_names and setting.readable_name then
|
||||
return fgettext(setting.readable_name)
|
||||
end
|
||||
return setting.name
|
||||
end
|
||||
|
||||
|
||||
local function is_valid_number(value)
|
||||
return type(value) == "number" and not (value ~= value or value >= math.huge or value <= -math.huge)
|
||||
end
|
||||
|
||||
|
||||
function make.heading(text)
|
||||
return {
|
||||
full_width = true,
|
||||
get_formspec = function(self, avail_w)
|
||||
return ("label[0,0.6;%s]box[0,0.9;%f,0.05;#ccc6]"):format(core.formspec_escape(text), avail_w), 1.2
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function make.note(text)
|
||||
return {
|
||||
full_width = true,
|
||||
get_formspec = function(self, avail_w)
|
||||
-- Assuming label height 0.4:
|
||||
-- Position at y=0 to eat 0.2 of the padding above, leave 0.05.
|
||||
-- The returned used_height doesn't include padding.
|
||||
return ("label[0,0;%s]"):format(core.colorize("#bbb", core.formspec_escape(text))), 0.2
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
--- Used for string and numeric style fields
|
||||
---
|
||||
--- @param converter Function to coerce values from strings.
|
||||
--- @param validator Validator function, optional. Returns true when valid.
|
||||
--- @param stringifier Function to convert values to strings, optional.
|
||||
local function make_field(converter, validator, stringifier)
|
||||
return function(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get(setting.name) or setting.default
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
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 .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set"))
|
||||
|
||||
return fs, 1.1
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
if fields["set_" .. setting.name] or fields.key_enter_field == setting.name then
|
||||
local value = converter(fields[setting.name])
|
||||
if value == nil or (validator and not validator(value)) then
|
||||
return true
|
||||
end
|
||||
|
||||
if setting.min then
|
||||
value = math.max(value, setting.min)
|
||||
end
|
||||
if setting.max then
|
||||
value = math.min(value, setting.max)
|
||||
end
|
||||
core.settings:set(setting.name, (stringifier or tostring)(value))
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
make.float = make_field(tonumber, is_valid_number, function(x)
|
||||
local str = tostring(x)
|
||||
if str:match("^[+-]?%d+$") then
|
||||
str = str .. ".0"
|
||||
end
|
||||
return str
|
||||
end)
|
||||
make.int = make_field(function(x)
|
||||
local value = tonumber(x)
|
||||
return value and math.floor(value)
|
||||
end, is_valid_number)
|
||||
make.string = make_field(tostring, nil)
|
||||
|
||||
|
||||
function make.bool(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get_bool(setting.name, core.is_yes(setting.default))
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local fs = ("checkbox[0,0.25;%s;%s;%s]"):format(
|
||||
setting.name, get_label(setting), tostring(value))
|
||||
return fs, 0.5
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
if fields[setting.name] == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
core.settings:set_bool(setting.name, core.is_yes(fields[setting.name]))
|
||||
return true
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function make.enum(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
max_w = 4.5,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get(setting.name) or setting.default
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local labels = setting.option_labels or {}
|
||||
|
||||
local items = {}
|
||||
for i, option in ipairs(setting.values) do
|
||||
items[i] = core.formspec_escape(labels[option] or option)
|
||||
end
|
||||
|
||||
local selected_idx = table.indexof(setting.values, value)
|
||||
local fs = "label[0,0.1;" .. get_label(setting) .. "]"
|
||||
|
||||
fs = fs .. ("dropdown[0,0.3;%f,0.8;%s;%s;%d;true]"):format(
|
||||
avail_w, setting.name, table.concat(items, ","), selected_idx, value)
|
||||
|
||||
return fs, 1.1
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
local old_value = core.settings:get(setting.name) or setting.default
|
||||
local idx = tonumber(fields[setting.name]) or 0
|
||||
local value = setting.values[idx]
|
||||
if value == nil or value == old_value then
|
||||
return false
|
||||
end
|
||||
|
||||
core.settings:set(setting.name, value)
|
||||
return true
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function make_path(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = core.settings:get(setting.name) or setting.default
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
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 .. ("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"))
|
||||
|
||||
return fs, 1.1
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
local dialog_name = "dlg_path_" .. setting.name
|
||||
if fields["pick_" .. setting.name] then
|
||||
local is_file = setting.type ~= "path"
|
||||
core.show_path_select_dialog(dialog_name,
|
||||
is_file and fgettext_ne("Select file") or fgettext_ne("Select directory"), is_file)
|
||||
return true
|
||||
end
|
||||
if fields[dialog_name .. "_accepted"] then
|
||||
local value = fields[dialog_name .. "_accepted"]
|
||||
if value ~= nil then
|
||||
core.settings:set(setting.name, value)
|
||||
end
|
||||
return true
|
||||
end
|
||||
if fields["set_" .. setting.name] or fields.key_enter_field == setting.name then
|
||||
local value = fields[setting.name]
|
||||
if value ~= nil then
|
||||
core.settings:set(setting.name, value)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
if PLATFORM == "Android" then
|
||||
-- The Irrlicht file picker doesn't work on Android.
|
||||
make.path = make.string
|
||||
make.filepath = make.string
|
||||
else
|
||||
make.path = make_path
|
||||
make.filepath = make_path
|
||||
end
|
||||
|
||||
|
||||
function make.v3f(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local value = vector.from_string(core.settings:get(setting.name) or setting.default)
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
-- Allocate space for "Set" button
|
||||
avail_w = avail_w - 1
|
||||
|
||||
local fs = "label[0,0.1;" .. get_label(setting) .. "]"
|
||||
|
||||
local field_width = (avail_w - 3*0.25) / 3
|
||||
|
||||
fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format(
|
||||
0, field_width, setting.name .. "_x", "X", value.x)
|
||||
fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format(
|
||||
field_width + 0.25, field_width, setting.name .. "_y", "Y", value.y)
|
||||
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 .. ("button[%f,0.6;1,0.8;%s;%s]"):format(avail_w, "set_" .. setting.name, fgettext("Set"))
|
||||
|
||||
return fs, 1.4
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
if fields["set_" .. setting.name] or
|
||||
fields.key_enter_field == setting.name .. "_x" or
|
||||
fields.key_enter_field == setting.name .. "_y" or
|
||||
fields.key_enter_field == setting.name .. "_z" then
|
||||
local x = tonumber(fields[setting.name .. "_x"])
|
||||
local y = tonumber(fields[setting.name .. "_y"])
|
||||
local z = tonumber(fields[setting.name .. "_z"])
|
||||
if is_valid_number(x) and is_valid_number(y) and is_valid_number(z) then
|
||||
core.settings:set(setting.name, vector.new(x, y, z):to_string())
|
||||
else
|
||||
core.log("error", "Invalid vector: " .. dump({x, y, z}))
|
||||
end
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function make.flags(setting)
|
||||
local checkboxes = {}
|
||||
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
local fs = {
|
||||
"label[0,0.1;" .. get_label(setting) .. "]",
|
||||
}
|
||||
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
checkboxes = {}
|
||||
for _, name in ipairs(setting.possible) do
|
||||
checkboxes[name] = false
|
||||
end
|
||||
local function apply_flags(flag_string, what)
|
||||
local prefixed_flags = {}
|
||||
for _, name in ipairs(flag_string:split(",")) do
|
||||
prefixed_flags[name:trim()] = true
|
||||
end
|
||||
for _, name in ipairs(setting.possible) do
|
||||
local enabled = prefixed_flags[name]
|
||||
local disabled = prefixed_flags["no" .. name]
|
||||
if enabled and disabled then
|
||||
core.log("warning", "Flag " .. name .. " in " .. what .. " " ..
|
||||
setting.name .. " both enabled and disabled, ignoring")
|
||||
elseif enabled then
|
||||
checkboxes[name] = true
|
||||
elseif disabled then
|
||||
checkboxes[name] = false
|
||||
end
|
||||
end
|
||||
end
|
||||
-- First apply the default, which is necessary since flags
|
||||
-- which are not overridden may be missing from the value.
|
||||
apply_flags(setting.default, "default for setting")
|
||||
local value = core.settings:get(setting.name)
|
||||
if value then
|
||||
apply_flags(value, "setting")
|
||||
end
|
||||
|
||||
local columns = math.max(math.floor(avail_w / 2.5), 1)
|
||||
local column_width = avail_w / columns
|
||||
local x = 0
|
||||
local y = 0.55
|
||||
|
||||
for _, possible in ipairs(setting.possible) do
|
||||
if x >= avail_w then
|
||||
x = 0
|
||||
y = y + 0.5
|
||||
end
|
||||
|
||||
local is_checked = checkboxes[possible]
|
||||
fs[#fs + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format(
|
||||
x, y, setting.name .. "_" .. possible,
|
||||
core.formspec_escape(possible), tostring(is_checked))
|
||||
x = x + column_width
|
||||
end
|
||||
|
||||
return table.concat(fs, ""), y + 0.25
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields)
|
||||
local changed = false
|
||||
for name, _ in pairs(checkboxes) do
|
||||
local value = fields[setting.name .. "_" .. name]
|
||||
if value ~= nil then
|
||||
checkboxes[name] = core.is_yes(value)
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
|
||||
if changed then
|
||||
local values = {}
|
||||
for _, name in ipairs(setting.possible) do
|
||||
if checkboxes[name] then
|
||||
table.insert(values, name)
|
||||
else
|
||||
table.insert(values, "no" .. name)
|
||||
end
|
||||
end
|
||||
|
||||
core.settings:set(setting.name, table.concat(values, ","))
|
||||
end
|
||||
return changed
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function make_noise_params(setting)
|
||||
return {
|
||||
info_text = setting.comment,
|
||||
setting = setting,
|
||||
|
||||
get_formspec = function(self, avail_w)
|
||||
-- The "defaults" noise parameter flag doesn't reset a noise
|
||||
-- setting to its default value, so we offer a regular reset button.
|
||||
self.resettable = core.settings:has(setting.name)
|
||||
|
||||
local fs = "label[0,0.4;" .. get_label(setting) .. "]" ..
|
||||
("button[%f,0;2.5,0.8;%s;%s]"):format(avail_w - 2.5, "edit_" .. setting.name, fgettext("Edit"))
|
||||
return fs, 0.8
|
||||
end,
|
||||
|
||||
on_submit = function(self, fields, tabview)
|
||||
if fields["edit_" .. setting.name] then
|
||||
local dlg = create_change_mapgen_flags_dlg(setting)
|
||||
dlg:set_parent(tabview)
|
||||
tabview:hide()
|
||||
dlg:show()
|
||||
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
make.noise_params_2d = make_noise_params
|
||||
make.noise_params_3d = make_noise_params
|
||||
|
||||
|
||||
return make
|
|
@ -1,252 +0,0 @@
|
|||
--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.
|
||||
|
||||
|
||||
local checkboxes = {}
|
||||
|
||||
local function flags_to_table(flags)
|
||||
return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split
|
||||
end
|
||||
|
||||
local function get_current_np_group(setting)
|
||||
local value = core.settings:get_np_group(setting.name)
|
||||
if value == nil then
|
||||
return setting.values
|
||||
end
|
||||
local p = "%g"
|
||||
return {
|
||||
p:format(value.offset),
|
||||
p:format(value.scale),
|
||||
p:format(value.spread.x),
|
||||
p:format(value.spread.y),
|
||||
p:format(value.spread.z),
|
||||
p:format(value.seed),
|
||||
p:format(value.octaves),
|
||||
p:format(value.persistence),
|
||||
p:format(value.lacunarity),
|
||||
value.flags
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function get_formspec(dialogdata)
|
||||
local setting = dialogdata.setting
|
||||
|
||||
-- Final formspec will be created at the end of this function
|
||||
-- Default values below, may be changed depending on setting type
|
||||
local width = 10
|
||||
local height = 2
|
||||
local description_height = 1.5
|
||||
|
||||
local t = get_current_np_group(setting)
|
||||
local dimension = 3
|
||||
if setting.type == "noise_params_2d" then
|
||||
dimension = 2
|
||||
end
|
||||
|
||||
local fields = {}
|
||||
local function add_field(x, name, label, value)
|
||||
fields[#fields + 1] = ("field[%f,%f;3.3,1;%s;%s;%s]"):format(
|
||||
x, height, name, label, core.formspec_escape(value or "")
|
||||
)
|
||||
end
|
||||
-- First row
|
||||
height = height + 0.3
|
||||
add_field(0.3, "te_offset", fgettext("Offset"), t[1])
|
||||
add_field(3.6, "te_scale", fgettext("Scale"), t[2])
|
||||
add_field(6.9, "te_seed", fgettext("Seed"), t[6])
|
||||
height = height + 1.1
|
||||
|
||||
-- Second row
|
||||
add_field(0.3, "te_spreadx", fgettext("X spread"), t[3])
|
||||
if dimension == 3 then
|
||||
add_field(3.6, "te_spready", fgettext("Y spread"), t[4])
|
||||
else
|
||||
fields[#fields + 1] = "label[4," .. height - 0.2 .. ";" ..
|
||||
fgettext("2D Noise") .. "]"
|
||||
end
|
||||
add_field(6.9, "te_spreadz", fgettext("Z spread"), t[5])
|
||||
height = height + 1.1
|
||||
|
||||
-- Third row
|
||||
add_field(0.3, "te_octaves", fgettext("Octaves"), t[7])
|
||||
add_field(3.6, "te_persist", fgettext("Persistence"), t[8])
|
||||
add_field(6.9, "te_lacun", fgettext("Lacunarity"), t[9])
|
||||
height = height + 1.1
|
||||
|
||||
|
||||
local enabled_flags = flags_to_table(t[10])
|
||||
local flags = {}
|
||||
for _, name in ipairs(enabled_flags) do
|
||||
-- Index by name, to avoid iterating over all enabled_flags for every possible flag.
|
||||
flags[name] = true
|
||||
end
|
||||
for _, name in ipairs(setting.flags) do
|
||||
local checkbox_name = "cb_" .. name
|
||||
local is_enabled = flags[name] == true -- to get false if nil
|
||||
checkboxes[checkbox_name] = is_enabled
|
||||
end
|
||||
|
||||
local formspec = table.concat(fields)
|
||||
.. "checkbox[0.5," .. height - 0.6 .. ";cb_defaults;"
|
||||
--[[~ "defaults" is a noise parameter flag.
|
||||
It describes the default processing options
|
||||
for noise settings in the settings menu. ]]
|
||||
.. fgettext("defaults") .. ";" -- defaults
|
||||
.. tostring(flags["defaults"] == true) .. "]" -- to get false if nil
|
||||
.. "checkbox[5," .. height - 0.6 .. ";cb_eased;"
|
||||
--[[~ "eased" is a noise parameter flag.
|
||||
It is used to make the map smoother and
|
||||
can be enabled in noise settings in
|
||||
the settings menu. ]]
|
||||
.. fgettext("eased") .. ";" -- eased
|
||||
.. tostring(flags["eased"] == true) .. "]"
|
||||
.. "checkbox[5," .. height - 0.15 .. ";cb_absvalue;"
|
||||
--[[~ "absvalue" is a noise parameter flag.
|
||||
It is short for "absolute value".
|
||||
It can be enabled in noise settings in
|
||||
the settings menu. ]]
|
||||
.. fgettext("absvalue") .. ";" -- absvalue
|
||||
.. tostring(flags["absvalue"] == true) .. "]"
|
||||
|
||||
height = height + 1
|
||||
|
||||
-- Box good, textarea bad. Calculate textarea size from box.
|
||||
local function create_textfield(size, label, text, bg_color)
|
||||
local textarea = {
|
||||
x = size.x + 0.3,
|
||||
y = size.y,
|
||||
w = size.w + 0.25,
|
||||
h = size.h * 1.16 + 0.12
|
||||
}
|
||||
return ("box[%f,%f;%f,%f;%s]textarea[%f,%f;%f,%f;;%s;%s]"):format(
|
||||
size.x, size.y, size.w, size.h, bg_color or "#000",
|
||||
textarea.x, textarea.y, textarea.w, textarea.h,
|
||||
core.formspec_escape(label), core.formspec_escape(text)
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
-- When there's an error: Shrink description textarea and add error below
|
||||
if dialogdata.error_message then
|
||||
local error_box = {
|
||||
x = 0,
|
||||
y = description_height - 0.4,
|
||||
w = width - 0.25,
|
||||
h = 0.5
|
||||
}
|
||||
formspec = formspec ..
|
||||
create_textfield(error_box, "", dialogdata.error_message, "#600")
|
||||
description_height = description_height - 0.75
|
||||
end
|
||||
|
||||
-- Get description field
|
||||
local description_box = {
|
||||
x = 0,
|
||||
y = 0.2,
|
||||
w = width - 0.25,
|
||||
h = description_height
|
||||
}
|
||||
|
||||
local setting_name = setting.name
|
||||
if setting.readable_name then
|
||||
setting_name = fgettext_ne(setting.readable_name) ..
|
||||
" (" .. setting.name .. ")"
|
||||
end
|
||||
|
||||
local comment_text
|
||||
if setting.comment == "" then
|
||||
comment_text = fgettext_ne("(No description of setting given)")
|
||||
else
|
||||
comment_text = fgettext_ne(setting.comment)
|
||||
end
|
||||
|
||||
return (
|
||||
"size[" .. width .. "," .. height + 0.25 .. ",true]" ..
|
||||
create_textfield(description_box, setting_name, comment_text) ..
|
||||
formspec ..
|
||||
"button[" .. width / 2 - 2.5 .. "," .. height - 0.4 .. ";2.5,1;btn_done;" ..
|
||||
fgettext("Save") .. "]" ..
|
||||
"button[" .. width / 2 .. "," .. height - 0.4 .. ";2.5,1;btn_cancel;" ..
|
||||
fgettext("Cancel") .. "]"
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
local function buttonhandler(this, fields)
|
||||
local setting = this.data.setting
|
||||
if fields["btn_done"] or fields["key_enter"] then
|
||||
local np_flags = {}
|
||||
for _, name in ipairs(setting.flags) do
|
||||
if checkboxes["cb_" .. name] then
|
||||
table.insert(np_flags, name)
|
||||
end
|
||||
end
|
||||
|
||||
checkboxes = {}
|
||||
|
||||
if setting.type == "noise_params_2d" then
|
||||
fields["te_spready"] = fields["te_spreadz"]
|
||||
end
|
||||
local new_value = {
|
||||
offset = fields["te_offset"],
|
||||
scale = fields["te_scale"],
|
||||
spread = {
|
||||
x = fields["te_spreadx"],
|
||||
y = fields["te_spready"],
|
||||
z = fields["te_spreadz"]
|
||||
},
|
||||
seed = fields["te_seed"],
|
||||
octaves = fields["te_octaves"],
|
||||
persistence = fields["te_persist"],
|
||||
lacunarity = fields["te_lacun"],
|
||||
flags = table.concat(np_flags, ", ")
|
||||
}
|
||||
core.settings:set_np_group(setting.name, new_value)
|
||||
|
||||
core.settings:write()
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["btn_cancel"] then
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
||||
for name, value in pairs(fields) do
|
||||
if name:sub(1, 3) == "cb_" then
|
||||
checkboxes[name] = core.is_yes(value)
|
||||
return false -- Don't update the formspec!
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function create_change_mapgen_flags_dlg(setting)
|
||||
assert(type(setting) == "table")
|
||||
|
||||
local retval = dialog_create("dlg_change_mapgen_flags",
|
||||
get_formspec,
|
||||
buttonhandler,
|
||||
nil)
|
||||
|
||||
retval.data.setting = setting
|
||||
return retval
|
||||
end
|
|
@ -1,775 +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 component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
|
||||
"settings" .. DIR_DELIM .. "components.lua")
|
||||
|
||||
local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
|
||||
"settings" .. DIR_DELIM .. "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 = {}
|
||||
local page_by_id = {}
|
||||
local filtered_pages = all_pages
|
||||
local filtered_page_by_id = page_by_id
|
||||
|
||||
|
||||
local function get_setting_info(name)
|
||||
for _, entry in ipairs(full_settings) do
|
||||
if entry.type ~= "category" and entry.name == name then
|
||||
return entry
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local function add_page(page)
|
||||
assert(type(page.id) == "string")
|
||||
assert(type(page.title) == "string")
|
||||
assert(page.section == nil or type(page.section) == "string")
|
||||
assert(type(page.content) == "table")
|
||||
|
||||
assert(not page_by_id[page.id], "Page " .. page.id .. " already registered")
|
||||
|
||||
all_pages[#all_pages + 1] = page
|
||||
page_by_id[page.id] = page
|
||||
return page
|
||||
end
|
||||
|
||||
|
||||
local function load_settingtypes()
|
||||
local page = nil
|
||||
local section = nil
|
||||
local function ensure_page_started()
|
||||
if not page then
|
||||
page = add_page({
|
||||
id = (section or "general"):lower():gsub(" ", "_"),
|
||||
title = section or fgettext_ne("General"),
|
||||
section = section,
|
||||
content = {},
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
for _, entry in ipairs(full_settings) do
|
||||
if entry.type == "category" then
|
||||
if entry.level == 0 then
|
||||
section = entry.name
|
||||
page = nil
|
||||
elseif entry.level == 1 then
|
||||
page = {
|
||||
id = ((section and section .. "_" or "") .. entry.name):lower():gsub(" ", "_"),
|
||||
title = entry.readable_name or entry.name,
|
||||
section = section,
|
||||
content = {},
|
||||
}
|
||||
|
||||
page = add_page(page)
|
||||
elseif entry.level == 2 then
|
||||
ensure_page_started()
|
||||
page.content[#page.content + 1] = {
|
||||
heading = fgettext_ne(entry.readable_name or entry.name),
|
||||
}
|
||||
end
|
||||
else
|
||||
ensure_page_started()
|
||||
page.content[#page.content + 1] = entry.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function load()
|
||||
if loaded then
|
||||
return
|
||||
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,
|
||||
}
|
||||
|
||||
local touchscreen_layout = {
|
||||
query_text = "Touchscreen layout",
|
||||
requires = {
|
||||
touchscreen = true,
|
||||
},
|
||||
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
|
||||
end,
|
||||
on_submit = function(self, fields)
|
||||
if fields.btn_touch_layout then
|
||||
core.show_touchscreen_layout()
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
add_page({
|
||||
id = "accessibility",
|
||||
title = fgettext_ne("Accessibility"),
|
||||
content = {
|
||||
"language",
|
||||
{ heading = fgettext_ne("General") },
|
||||
"font_size",
|
||||
"chat_font_size",
|
||||
"gui_scaling",
|
||||
"hud_scaling",
|
||||
"show_nametag_backgrounds",
|
||||
{ heading = fgettext_ne("Chat") },
|
||||
"console_height",
|
||||
"console_alpha",
|
||||
"console_color",
|
||||
{ heading = fgettext_ne("Controls") },
|
||||
"autojump",
|
||||
"safe_dig_and_place",
|
||||
{ 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
|
||||
local content = page_by_id.graphics_and_audio_effects.content
|
||||
local idx = table.indexof(content, "enable_dynamic_shadows")
|
||||
table.insert(content, idx, shadows_component)
|
||||
|
||||
idx = table.indexof(content, "enable_auto_exposure") + 1
|
||||
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
|
||||
table.insert(content, idx, note)
|
||||
|
||||
idx = table.indexof(content, "enable_bloom") + 1
|
||||
note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)"))
|
||||
note.requires = get_setting_info("enable_bloom").requires
|
||||
table.insert(content, idx, note)
|
||||
|
||||
idx = table.indexof(content, "enable_volumetric_lighting") + 1
|
||||
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
|
||||
table.insert(content, idx, note)
|
||||
end
|
||||
|
||||
-- These must not be translated, as they need to show in the local
|
||||
-- language no matter the user's current language.
|
||||
-- This list must be kept in sync with src/unsupported_language_list.txt.
|
||||
get_setting_info("language").option_labels = {
|
||||
[""] = fgettext_ne("(Use system language)"),
|
||||
--ar = " [ar]", blacklisted
|
||||
be = "Беларуская [be]",
|
||||
bg = "Български [bg]",
|
||||
ca = "Català [ca]",
|
||||
cs = "Česky [cs]",
|
||||
cy = "Cymraeg [cy]",
|
||||
da = "Dansk [da]",
|
||||
de = "Deutsch [de]",
|
||||
--dv = " [dv]", blacklisted
|
||||
el = "Ελληνικά [el]",
|
||||
en = "English [en]",
|
||||
eo = "Esperanto [eo]",
|
||||
es = "Español [es]",
|
||||
et = "Eesti [et]",
|
||||
eu = "Euskara [eu]",
|
||||
fi = "Suomi [fi]",
|
||||
fil = "Wikang Filipino [fil]",
|
||||
fr = "Français [fr]",
|
||||
gd = "Gàidhlig [gd]",
|
||||
gl = "Galego [gl]",
|
||||
--he = " [he]", blacklisted
|
||||
--hi = " [hi]", blacklisted
|
||||
hu = "Magyar [hu]",
|
||||
id = "Bahasa Indonesia [id]",
|
||||
it = "Italiano [it]",
|
||||
ja = "日本語 [ja]",
|
||||
jbo = "Lojban [jbo]",
|
||||
kk = "Қазақша [kk]",
|
||||
--kn = " [kn]", blacklisted
|
||||
ko = "한국어 [ko]",
|
||||
ky = "Kırgızca / Кыргызча [ky]",
|
||||
lt = "Lietuvių [lt]",
|
||||
lv = "Latviešu [lv]",
|
||||
mn = "Монгол [mn]",
|
||||
mr = "मराठी [mr]",
|
||||
ms = "Bahasa Melayu [ms]",
|
||||
--ms_Arab = " [ms_Arab]", blacklisted
|
||||
nb = "Norsk Bokmål [nb]",
|
||||
nl = "Nederlands [nl]",
|
||||
nn = "Norsk Nynorsk [nn]",
|
||||
oc = "Occitan [oc]",
|
||||
pl = "Polski [pl]",
|
||||
pt = "Português [pt]",
|
||||
pt_BR = "Português do Brasil [pt_BR]",
|
||||
ro = "Română [ro]",
|
||||
ru = "Русский [ru]",
|
||||
sk = "Slovenčina [sk]",
|
||||
sl = "Slovenščina [sl]",
|
||||
sr_Cyrl = "Српски [sr_Cyrl]",
|
||||
sr_Latn = "Srpski (Latinica) [sr_Latn]",
|
||||
sv = "Svenska [sv]",
|
||||
sw = "Kiswahili [sw]",
|
||||
--th = " [th]", blacklisted
|
||||
tr = "Türkçe [tr]",
|
||||
tt = "Tatarça [tt]",
|
||||
uk = "Українська [uk]",
|
||||
vi = "Tiếng Việt [vi]",
|
||||
zh_CN = "中文 (简体) [zh_CN]",
|
||||
zh_TW = "正體中文 (繁體) [zh_TW]",
|
||||
}
|
||||
|
||||
get_setting_info("touch_controls").option_labels = {
|
||||
["auto"] = fgettext_ne("Auto"),
|
||||
["true"] = fgettext_ne("Enabled"),
|
||||
["false"] = fgettext_ne("Disabled"),
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
-- See if setting matches keywords
|
||||
local function get_setting_match_weight(entry, query_keywords)
|
||||
local setting_score = 0
|
||||
for _, keyword in ipairs(query_keywords) do
|
||||
if string.find(entry.name:lower(), keyword, 1, true) then
|
||||
setting_score = setting_score + 1
|
||||
end
|
||||
|
||||
if entry.readable_name and
|
||||
string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then
|
||||
setting_score = setting_score + 1
|
||||
end
|
||||
|
||||
if entry.comment and
|
||||
string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then
|
||||
setting_score = setting_score + 1
|
||||
end
|
||||
end
|
||||
|
||||
return setting_score
|
||||
end
|
||||
|
||||
|
||||
local function filter_page_content(page, query_keywords)
|
||||
if #query_keywords == 0 then
|
||||
return page.content, 0
|
||||
end
|
||||
|
||||
local retval = {}
|
||||
local i = 1
|
||||
local max_weight = 0
|
||||
for _, content in ipairs(page.content) do
|
||||
if type(content) == "string" then
|
||||
local setting = get_setting_info(content)
|
||||
assert(setting, "Unknown setting: " .. content)
|
||||
|
||||
local weight = get_setting_match_weight(setting, query_keywords)
|
||||
if weight > 0 then
|
||||
max_weight = math.max(max_weight, weight)
|
||||
retval[i] = content
|
||||
i = i + 1
|
||||
end
|
||||
elseif type(content) == "table" and content.query_text then
|
||||
for _, keyword in ipairs(query_keywords) do
|
||||
if string.find(fgettext(content.query_text), keyword, 1, true) then
|
||||
max_weight = math.max(max_weight, 1)
|
||||
retval[i] = content
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return retval, max_weight
|
||||
end
|
||||
|
||||
|
||||
local function update_filtered_pages(query)
|
||||
filtered_pages = {}
|
||||
filtered_page_by_id = {}
|
||||
|
||||
local query_keywords = {}
|
||||
for word in query:lower():gmatch("%S+") do
|
||||
table.insert(query_keywords, word)
|
||||
end
|
||||
|
||||
local best_page = nil
|
||||
local best_page_weight = -1
|
||||
|
||||
for _, page in ipairs(all_pages) do
|
||||
local content, page_weight = filter_page_content(page, query_keywords)
|
||||
if page_has_contents(page, content) then
|
||||
local new_page = table.copy(page)
|
||||
new_page.content = content
|
||||
|
||||
filtered_pages[#filtered_pages + 1] = new_page
|
||||
filtered_page_by_id[new_page.id] = new_page
|
||||
|
||||
if page_weight > best_page_weight then
|
||||
best_page = new_page
|
||||
best_page_weight = page_weight
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return best_page and best_page.id or nil
|
||||
end
|
||||
|
||||
|
||||
local function check_requirements(name, requires)
|
||||
if requires == nil then
|
||||
return true
|
||||
end
|
||||
|
||||
local video_driver = core.get_active_driver()
|
||||
local touch_support = core.irrlicht_device_supports_touch()
|
||||
local touch_controls = core.settings:get("touch_controls")
|
||||
local special = {
|
||||
android = PLATFORM == "Android",
|
||||
desktop = PLATFORM ~= "Android",
|
||||
touch_support = touch_support,
|
||||
-- When touch_controls is "auto", we don't know which input method will
|
||||
-- be used, so we show settings for both.
|
||||
touchscreen = touch_support and (touch_controls == "auto" or core.is_yes(touch_controls)),
|
||||
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",
|
||||
}
|
||||
|
||||
for req_key, req_value in pairs(requires) do
|
||||
if special[req_key] == nil then
|
||||
local required_setting = get_setting_info(req_key)
|
||||
if required_setting == nil then
|
||||
core.log("warning", "Unknown setting " .. req_key .. " required by " .. (name or "???"))
|
||||
end
|
||||
local actual_value = core.settings:get_bool(req_key,
|
||||
required_setting and core.is_yes(required_setting.default))
|
||||
if actual_value ~= req_value then
|
||||
return false
|
||||
end
|
||||
elseif special[req_key] ~= req_value then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function page_has_contents(page, actual_content)
|
||||
local is_advanced =
|
||||
page.id:sub(1, #"client_and_server") == "client_and_server" or
|
||||
page.id:sub(1, #"mapgen") == "mapgen" or
|
||||
page.id:sub(1, #"advanced") == "advanced"
|
||||
local show_advanced = core.settings:get_bool("show_advanced")
|
||||
if is_advanced and not show_advanced then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, item in ipairs(actual_content) do
|
||||
if item == false or item.heading then --luacheck: ignore
|
||||
-- skip
|
||||
elseif type(item) == "string" then
|
||||
local setting = get_setting_info(item)
|
||||
assert(setting, "Unknown setting: " .. item)
|
||||
if check_requirements(setting.name, setting.requires) then
|
||||
return true
|
||||
end
|
||||
elseif item.get_formspec then
|
||||
if check_requirements(item.id, item.requires) then
|
||||
return true
|
||||
end
|
||||
else
|
||||
error("Unknown content in page: " .. dump(item))
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function build_page_components(page)
|
||||
-- Filter settings based on requirements
|
||||
local content = {}
|
||||
local last_heading
|
||||
for _, item in ipairs(page.content) do
|
||||
if item == false then --luacheck: ignore
|
||||
-- skip
|
||||
elseif item.heading then
|
||||
last_heading = item
|
||||
else
|
||||
local name, requires
|
||||
if type(item) == "string" then
|
||||
local setting = get_setting_info(item)
|
||||
assert(setting, "Unknown setting: " .. item)
|
||||
name = setting.name
|
||||
requires = setting.requires
|
||||
elseif item.get_formspec then
|
||||
name = item.id
|
||||
requires = item.requires
|
||||
else
|
||||
error("Unknown content in page: " .. dump(item))
|
||||
end
|
||||
|
||||
if check_requirements(name, requires) then
|
||||
if last_heading then
|
||||
content[#content + 1] = last_heading
|
||||
last_heading = nil
|
||||
end
|
||||
content[#content + 1] = item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Create components
|
||||
local retval = {}
|
||||
for i, item in ipairs(content) do
|
||||
if type(item) == "string" then
|
||||
local setting = get_setting_info(item)
|
||||
local component_func = component_funcs[setting.type]
|
||||
assert(component_func, "Unknown setting type: " .. setting.type)
|
||||
retval[i] = component_func(setting)
|
||||
elseif item.get_formspec then
|
||||
retval[i] = item
|
||||
elseif item.heading then
|
||||
retval[i] = component_funcs.heading(item.heading)
|
||||
end
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
|
||||
local formspec_show_hack = false
|
||||
|
||||
|
||||
local function get_formspec(dialogdata)
|
||||
local page_id = dialogdata.page_id or "accessibility"
|
||||
local page = filtered_page_by_id[page_id]
|
||||
|
||||
local extra_h = 1 -- not included in tabsize.height
|
||||
local tabsize = {
|
||||
width = core.settings:get_bool("touch_gui") and 16.5 or 15.5,
|
||||
height = core.settings:get_bool("touch_gui") and (10 - extra_h) or 12,
|
||||
}
|
||||
|
||||
local scrollbar_w = core.settings:get_bool("touch_gui") and 0.6 or 0.4
|
||||
|
||||
local left_pane_width = core.settings:get_bool("touch_gui") and 4.5 or 4.25
|
||||
local left_pane_padding = 0.25
|
||||
local search_width = left_pane_width + scrollbar_w - (0.75 * 2)
|
||||
|
||||
local back_w = 3
|
||||
local checkbox_w = (tabsize.width - back_w - 2*0.2) / 2
|
||||
local show_technical_names = core.settings:get_bool("show_technical_names")
|
||||
local show_advanced = core.settings:get_bool("show_advanced")
|
||||
|
||||
formspec_show_hack = not formspec_show_hack
|
||||
|
||||
local fs = {
|
||||
"formspec_version[6]",
|
||||
"size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]",
|
||||
core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "",
|
||||
"bgcolor[#0000]",
|
||||
|
||||
-- HACK: this is needed to allow resubmitting the same formspec
|
||||
formspec_show_hack and " " or "",
|
||||
|
||||
"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")),
|
||||
|
||||
("box[%f,%f;%f,0.8;#0000008C]"):format(
|
||||
back_w + 0.2, tabsize.height + 0.2, checkbox_w),
|
||||
("checkbox[%f,%f;show_technical_names;%s;%s]"):format(
|
||||
back_w + 2*0.2, tabsize.height + 0.6,
|
||||
fgettext("Show technical names"), tostring(show_technical_names)),
|
||||
|
||||
("box[%f,%f;%f,0.8;#0000008C]"):format(
|
||||
back_w + 2*0.2 + checkbox_w, tabsize.height + 0.2, checkbox_w),
|
||||
("checkbox[%f,%f;show_advanced;%s;%s]"):format(
|
||||
back_w + 3*0.2 + checkbox_w, tabsize.height + 0.6,
|
||||
fgettext("Show advanced settings"), tostring(show_advanced)),
|
||||
|
||||
"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]",
|
||||
"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;]",
|
||||
"tooltip[search;", fgettext("Search"), "]",
|
||||
"tooltip[search_clear;", fgettext("Clear"), "]",
|
||||
"container_end[]",
|
||||
("scroll_container[0.25,1.25;%f,%f;leftscroll;vertical;0.1;0]"):format(
|
||||
left_pane_width, tabsize.height - 1.5),
|
||||
"style_type[button;border=false;bgcolor=#3333]",
|
||||
"style_type[button:hover;border=false;bgcolor=#6663]",
|
||||
}
|
||||
|
||||
local y = 0
|
||||
local last_section = nil
|
||||
for _, other_page in ipairs(filtered_pages) do
|
||||
if other_page.section ~= last_section then
|
||||
fs[#fs + 1] = ("label[0.1,%f;%s]"):format(
|
||||
y + 0.41, core.colorize("#ff0", fgettext(other_page.section)))
|
||||
last_section = other_page.section
|
||||
y = y + 0.82
|
||||
end
|
||||
fs[#fs + 1] = ("box[0,%f;%f,0.8;%s]"):format(
|
||||
y, left_pane_width-left_pane_padding, other_page.id == page_id and "#467832FF" or "#3339")
|
||||
fs[#fs + 1] = ("button[0,%f;%f,0.8;page_%s;%s]")
|
||||
:format(y, left_pane_width-left_pane_padding, other_page.id, fgettext(other_page.title))
|
||||
y = y + 0.82
|
||||
end
|
||||
|
||||
if #filtered_pages == 0 then
|
||||
fs[#fs + 1] = "label[0.1,0.41;"
|
||||
fs[#fs + 1] = fgettext("No results")
|
||||
fs[#fs + 1] = "]"
|
||||
end
|
||||
|
||||
fs[#fs + 1] = "scroll_container_end[]"
|
||||
|
||||
if y >= tabsize.height - 1.25 then
|
||||
fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format(
|
||||
left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0)
|
||||
end
|
||||
|
||||
fs[#fs + 1] = "style_type[button;border=;bgcolor=]"
|
||||
|
||||
if not dialogdata.components then
|
||||
dialogdata.components = page and build_page_components(page) or {}
|
||||
end
|
||||
|
||||
local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25
|
||||
fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1;0.25]"):format(
|
||||
tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height)
|
||||
|
||||
y = 0.25
|
||||
for i, comp in ipairs(dialogdata.components) do
|
||||
fs[#fs + 1] = ("container[0,%f]"):format(y)
|
||||
|
||||
local avail_w = right_pane_width - 0.25
|
||||
if not comp.full_width then
|
||||
avail_w = avail_w - 1.4
|
||||
end
|
||||
if comp.max_w then
|
||||
avail_w = math.min(avail_w, comp.max_w)
|
||||
end
|
||||
|
||||
local comp_fs, used_h = comp:get_formspec(avail_w)
|
||||
fs[#fs + 1] = comp_fs
|
||||
|
||||
fs[#fs + 1] = "style_type[image_button;border=false;padding=]"
|
||||
|
||||
local show_reset = comp.resettable and comp.setting
|
||||
local show_info = comp.info_text and comp.info_text ~= ""
|
||||
if show_reset or show_info then
|
||||
-- ensure there's enough space for reset/info
|
||||
used_h = math.max(used_h, 0.5)
|
||||
end
|
||||
local info_reset_y = used_h / 2 - 0.25
|
||||
|
||||
if show_reset then
|
||||
local default = comp.setting.default
|
||||
local reset_tooltip = default and
|
||||
fgettext("Reset setting to default ($1)", tostring(default)) or
|
||||
fgettext("Reset setting to default")
|
||||
fs[#fs + 1] = ("image_button[%f,%f;0.5,0.5;%s;%s;]"):format(
|
||||
right_pane_width - 1.4, info_reset_y, reset_icon_path, "reset_" .. i)
|
||||
fs[#fs + 1] = ("tooltip[%s;%s]"):format("reset_" .. i, reset_tooltip)
|
||||
end
|
||||
|
||||
if show_info then
|
||||
local info_x = right_pane_width - 0.75
|
||||
fs[#fs + 1] = ("image[%f,%f;0.5,0.5;%s]"):format(info_x, info_reset_y, info_icon_path)
|
||||
fs[#fs + 1] = ("tooltip[%f,%f;0.5,0.5;%s]"):format(info_x, info_reset_y, fgettext(comp.info_text))
|
||||
end
|
||||
|
||||
fs[#fs + 1] = "style_type[image_button;border=;padding=]"
|
||||
|
||||
fs[#fs + 1] = "container_end[]"
|
||||
|
||||
if used_h > 0 then
|
||||
y = y + used_h + 0.25
|
||||
end
|
||||
end
|
||||
|
||||
fs[#fs + 1] = "scroll_container_end[]"
|
||||
|
||||
if y >= tabsize.height then
|
||||
fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format(
|
||||
tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0)
|
||||
end
|
||||
|
||||
return table.concat(fs, "")
|
||||
end
|
||||
|
||||
|
||||
-- On Android, closing the app via the "Recents screen" won't result in a clean
|
||||
-- exit, discarding any setting changes made by the user.
|
||||
-- To avoid that, we write the settings file in more cases on Android.
|
||||
function write_settings_early()
|
||||
if PLATFORM == "Android" then
|
||||
core.settings:write()
|
||||
end
|
||||
end
|
||||
|
||||
local function regenerate_page_list(dialogdata)
|
||||
local suggested_page_id = update_filtered_pages(dialogdata.query)
|
||||
|
||||
dialogdata.components = nil
|
||||
|
||||
if not filtered_page_by_id[dialogdata.page_id] then
|
||||
dialogdata.leftscroll = 0
|
||||
dialogdata.rightscroll = 0
|
||||
|
||||
dialogdata.page_id = suggested_page_id
|
||||
end
|
||||
end
|
||||
|
||||
local function buttonhandler(this, fields)
|
||||
local dialogdata = this.data
|
||||
dialogdata.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or dialogdata.leftscroll
|
||||
dialogdata.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or dialogdata.rightscroll
|
||||
dialogdata.query = fields.search_query
|
||||
|
||||
if fields.back then
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.show_technical_names ~= nil then
|
||||
local value = core.is_yes(fields.show_technical_names)
|
||||
core.settings:set_bool("show_technical_names", value)
|
||||
write_settings_early()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.show_advanced ~= nil then
|
||||
local value = core.is_yes(fields.show_advanced)
|
||||
core.settings:set_bool("show_advanced", value)
|
||||
write_settings_early()
|
||||
regenerate_page_list(dialogdata)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if fields.search or fields.key_enter_field == "search_query" then
|
||||
dialogdata.components = nil
|
||||
dialogdata.leftscroll = 0
|
||||
dialogdata.rightscroll = 0
|
||||
|
||||
dialogdata.page_id = update_filtered_pages(dialogdata.query)
|
||||
|
||||
return true
|
||||
end
|
||||
if fields.search_clear then
|
||||
dialogdata.query = ""
|
||||
dialogdata.components = nil
|
||||
dialogdata.leftscroll = 0
|
||||
dialogdata.rightscroll = 0
|
||||
|
||||
dialogdata.page_id = update_filtered_pages("")
|
||||
return true
|
||||
end
|
||||
|
||||
for _, page in ipairs(all_pages) do
|
||||
if fields["page_" .. page.id] then
|
||||
dialogdata.page_id = page.id
|
||||
dialogdata.components = nil
|
||||
dialogdata.rightscroll = 0
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function after_setting_change(comp)
|
||||
write_settings_early()
|
||||
if comp.setting and comp.setting.name == "touch_controls" then
|
||||
-- Changing the "touch_controls" setting may result in a different
|
||||
-- page list.
|
||||
regenerate_page_list(dialogdata)
|
||||
else
|
||||
-- Clear components so they regenerate
|
||||
dialogdata.components = nil
|
||||
end
|
||||
end
|
||||
|
||||
for i, comp in ipairs(dialogdata.components) do
|
||||
if comp.on_submit and comp:on_submit(fields, this) then
|
||||
after_setting_change(comp)
|
||||
return true
|
||||
end
|
||||
if comp.setting and fields["reset_" .. i] then
|
||||
core.settings:remove(comp.setting.name)
|
||||
after_setting_change(comp)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function eventhandler(event)
|
||||
if event == "DialogShow" then
|
||||
-- Don't show the header image behind the dialog.
|
||||
mm_game_theme.set_engine(true)
|
||||
return true
|
||||
end
|
||||
if event == "FullscreenChange" then
|
||||
-- Refresh the formspec to keep the fullscreen checkbox up to date.
|
||||
ui.update()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function create_settings_dlg()
|
||||
load()
|
||||
local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler)
|
||||
|
||||
dlg.data.page_id = update_filtered_pages("")
|
||||
|
||||
return dlg
|
||||
end
|
|
@ -1,139 +0,0 @@
|
|||
local concat = table.concat
|
||||
local insert = table.insert
|
||||
local sprintf = string.format
|
||||
local rep = string.rep
|
||||
|
||||
local minetest_example_header = [[
|
||||
# This file contains a list of all available settings and their default value for minetest.conf
|
||||
|
||||
# By default, all the settings are commented and not functional.
|
||||
# Uncomment settings by removing the preceding #.
|
||||
|
||||
# minetest.conf is read by default from:
|
||||
# ../minetest.conf
|
||||
# ../../minetest.conf
|
||||
# Any other path can be chosen by passing the path as a parameter
|
||||
# to the program, eg. "luanti.exe --config ../minetest.conf.example".
|
||||
|
||||
# Further documentation:
|
||||
# https://wiki.luanti.org/
|
||||
|
||||
]]
|
||||
|
||||
local group_format_template = [[
|
||||
# %s = {
|
||||
# offset = %s,
|
||||
# scale = %s,
|
||||
# spread = (%s, %s, %s),
|
||||
# seed = %s,
|
||||
# octaves = %s,
|
||||
# persistence = %s,
|
||||
# lacunarity = %s,
|
||||
# flags =%s
|
||||
# }
|
||||
|
||||
]]
|
||||
|
||||
local function create_minetest_conf_example(settings)
|
||||
local result = { minetest_example_header }
|
||||
for _, entry in ipairs(settings) do
|
||||
if entry.type == "category" then
|
||||
if entry.level == 0 then
|
||||
insert(result, "#\n# " .. entry.name .. "\n#\n\n")
|
||||
else
|
||||
insert(result, rep("#", entry.level))
|
||||
insert(result, "# " .. entry.name .. "\n\n")
|
||||
end
|
||||
else
|
||||
local group_format = false
|
||||
if entry.noise_params and entry.values then
|
||||
if entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then
|
||||
group_format = true
|
||||
end
|
||||
end
|
||||
if entry.comment ~= "" then
|
||||
for _, comment_line in ipairs(entry.comment:split("\n", true)) do
|
||||
if comment_line == "" then
|
||||
insert(result, "#\n")
|
||||
else
|
||||
insert(result, "# " .. comment_line .. "\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
if entry.type == "key" then
|
||||
local line = "See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h"
|
||||
insert(result, "# " .. line .. "\n")
|
||||
end
|
||||
insert(result, "# type: " .. entry.type)
|
||||
if entry.min then
|
||||
insert(result, " min: " .. entry.min)
|
||||
end
|
||||
if entry.max then
|
||||
insert(result, " max: " .. entry.max)
|
||||
end
|
||||
if entry.values and entry.noise_params == nil then
|
||||
insert(result, " values: " .. concat(entry.values, ", "))
|
||||
end
|
||||
if entry.possible then
|
||||
insert(result, " possible values: " .. concat(entry.possible, ", "))
|
||||
end
|
||||
insert(result, "\n")
|
||||
if group_format == true then
|
||||
local flags = entry.values[10]
|
||||
if flags ~= "" then
|
||||
flags = " "..flags
|
||||
end
|
||||
insert(result, sprintf(group_format_template, entry.name, entry.values[1],
|
||||
entry.values[2], entry.values[3], entry.values[4], entry.values[5],
|
||||
entry.values[6], entry.values[7], entry.values[8], entry.values[9],
|
||||
flags))
|
||||
else
|
||||
local append
|
||||
if entry.default ~= "" then
|
||||
append = " " .. entry.default
|
||||
end
|
||||
insert(result, sprintf("# %s =%s\n\n", entry.name, append or ""))
|
||||
end
|
||||
end
|
||||
end
|
||||
return concat(result)
|
||||
end
|
||||
|
||||
local translation_file_header = [[
|
||||
// This file is automatically generated
|
||||
// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files
|
||||
// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
|
||||
|
||||
fake_function() {]]
|
||||
|
||||
local function create_translation_file(settings)
|
||||
local result = { translation_file_header }
|
||||
for _, entry in ipairs(settings) do
|
||||
if entry.type == "category" then
|
||||
insert(result, sprintf("\tgettext(%q);", entry.name))
|
||||
else
|
||||
if entry.readable_name then
|
||||
insert(result, sprintf("\tgettext(%q);", entry.readable_name))
|
||||
end
|
||||
if entry.comment ~= "" then
|
||||
local comment_escaped = entry.comment:gsub("\n", "\\n")
|
||||
comment_escaped = comment_escaped:gsub("\"", "\\\"")
|
||||
insert(result, "\tgettext(\"" .. comment_escaped .. "\");")
|
||||
end
|
||||
end
|
||||
end
|
||||
insert(result, "}\n")
|
||||
return concat(result, "\n")
|
||||
end
|
||||
|
||||
local file = assert(io.open("minetest.conf.example", "w"))
|
||||
file:write(create_minetest_conf_example(settingtypes.parse_config_file(true, false)))
|
||||
file:close()
|
||||
|
||||
file = assert(io.open("src/settings_translation_file.cpp", "w"))
|
||||
-- If 'minetest.conf.example' appears in the 'bin' folder, the line below may have to be
|
||||
-- used instead. The file will also appear in the 'bin' folder.
|
||||
--file = assert(io.open("settings_translation_file.cpp", "w"))
|
||||
-- We don't want hidden settings to be translated, so we set read_all to false.
|
||||
file:write(create_translation_file(settingtypes.parse_config_file(false, false)))
|
||||
file:close()
|
|
@ -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")
|
|
@ -1,507 +0,0 @@
|
|||
--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.
|
||||
|
||||
settingtypes = {}
|
||||
|
||||
-- A Setting type is a table with the following keys:
|
||||
--
|
||||
-- name: Identifier
|
||||
-- readable_name: Readable title
|
||||
-- type: Category
|
||||
--
|
||||
-- name = mod.name,
|
||||
-- readable_name = mod.title,
|
||||
-- level = 1,
|
||||
-- type = "category", "int", "string", ""
|
||||
-- }
|
||||
|
||||
|
||||
local FILENAME = "settingtypes.txt"
|
||||
|
||||
local CHAR_CLASSES = {
|
||||
SPACE = "[%s]",
|
||||
VARIABLE = "[%w_%-%.]",
|
||||
INTEGER = "[+-]?[%d]",
|
||||
FLOAT = "[+-]?[%d%.]",
|
||||
FLAGS = "[%w_%-%.,]",
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
-- strip carriage returns (CR, /r)
|
||||
line = line:gsub("\r", "")
|
||||
|
||||
-- comment
|
||||
local comment_match = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
|
||||
if comment_match then
|
||||
settings.current_comment[#settings.current_comment + 1] = comment_match
|
||||
return
|
||||
end
|
||||
|
||||
-- clear current_comment so only comments directly above a setting are bound to it
|
||||
-- but keep a local reference to it for variables in the current line
|
||||
local current_comment = settings.current_comment
|
||||
settings.current_comment = {}
|
||||
|
||||
-- empty lines
|
||||
if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then
|
||||
return
|
||||
end
|
||||
|
||||
-- category
|
||||
local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
|
||||
if category then
|
||||
local category_level = stars:len() + base_level
|
||||
|
||||
if settings.current_hide_level then
|
||||
if settings.current_hide_level < category_level then
|
||||
-- Skip this category, it's inside a hidden category.
|
||||
return
|
||||
else
|
||||
-- The start of this category marks the end of a hidden category.
|
||||
settings.current_hide_level = nil
|
||||
end
|
||||
end
|
||||
|
||||
if not read_all and category:sub(1, 5) == "Hide:" then
|
||||
-- This category is hidden.
|
||||
settings.current_hide_level = category_level
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = category,
|
||||
level = category_level,
|
||||
type = "category",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if settings.current_hide_level then
|
||||
-- Ignore this line, we're inside a hidden category.
|
||||
return
|
||||
end
|
||||
|
||||
-- settings
|
||||
local first_part, name, readable_name, setting_type = line:match("^"
|
||||
-- this first capture group matches the whole first part,
|
||||
-- so we can later strip it from the rest of the line
|
||||
.. "("
|
||||
.. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name
|
||||
.. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "%(([^%)]*)%)" -- readable name
|
||||
.. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type
|
||||
.. CHAR_CLASSES.SPACE .. "*"
|
||||
.. ")")
|
||||
|
||||
if not first_part then
|
||||
return "Invalid line"
|
||||
end
|
||||
|
||||
if name:match("secure%.[.]*") and not allow_secure then
|
||||
return "Tried to add \"secure.\" setting"
|
||||
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
|
||||
local parts = last_line:sub(10):split(",")
|
||||
current_comment[#current_comment] = nil
|
||||
|
||||
for _, part in ipairs(parts) do
|
||||
part = part:trim()
|
||||
|
||||
local value = true
|
||||
if part:sub(1, 1) == "!" then
|
||||
value = false
|
||||
part = part:sub(2):trim()
|
||||
end
|
||||
|
||||
requires[part] = value
|
||||
end
|
||||
end
|
||||
|
||||
if readable_name == "" then
|
||||
readable_name = nil
|
||||
end
|
||||
local remaining_line = line:sub(first_part:len() + 1)
|
||||
|
||||
local comment = table.concat(current_comment, "\n"):trim()
|
||||
|
||||
if setting_type == "int" then
|
||||
local default, min, max = remaining_line:match("^"
|
||||
-- first int is required, the last 2 are optional
|
||||
.. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "(" .. CHAR_CLASSES.INTEGER .. "*)"
|
||||
.. "$")
|
||||
|
||||
if not default or not tonumber(default) then
|
||||
return "Invalid integer setting"
|
||||
end
|
||||
|
||||
min = tonumber(min)
|
||||
max = tonumber(max)
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = "int",
|
||||
default = default,
|
||||
min = min,
|
||||
max = max,
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if setting_type == "string"
|
||||
or setting_type == "key" or setting_type == "v3f" then
|
||||
local default = remaining_line:match("^(.*)$")
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = setting_type,
|
||||
default = default,
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if setting_type == "noise_params_2d"
|
||||
or setting_type == "noise_params_3d" then
|
||||
local default = remaining_line:match("^(.*)$")
|
||||
|
||||
if not default then
|
||||
return "Invalid string setting"
|
||||
end
|
||||
|
||||
local values = {}
|
||||
local ti = 1
|
||||
local index = 1
|
||||
for match in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
|
||||
index = default:find("[+-]?[%d.-e]+", index) + match:len()
|
||||
table.insert(values, match)
|
||||
ti = ti + 1
|
||||
if ti > 9 then
|
||||
break
|
||||
end
|
||||
end
|
||||
index = default:find("[^, ]", index)
|
||||
local flags = ""
|
||||
if index then
|
||||
flags = default:sub(index)
|
||||
end
|
||||
table.insert(values, flags)
|
||||
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = setting_type,
|
||||
default = default,
|
||||
default_table = {
|
||||
offset = values[1],
|
||||
scale = values[2],
|
||||
spread = {
|
||||
x = values[3],
|
||||
y = values[4],
|
||||
z = values[5]
|
||||
},
|
||||
seed = values[6],
|
||||
octaves = values[7],
|
||||
persistence = values[8],
|
||||
lacunarity = values[9],
|
||||
flags = values[10]
|
||||
},
|
||||
values = values,
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
noise_params = true,
|
||||
flags = flags_to_table("defaults,eased,absvalue")
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if setting_type == "bool" then
|
||||
if remaining_line ~= "false" and remaining_line ~= "true" then
|
||||
return "Invalid boolean setting"
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = "bool",
|
||||
default = remaining_line,
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if setting_type == "float" then
|
||||
local default, min, max = remaining_line:match("^"
|
||||
-- first float is required, the last 2 are optional
|
||||
.. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "(" .. CHAR_CLASSES.FLOAT .. "*)"
|
||||
.."$")
|
||||
|
||||
if not default or not tonumber(default) then
|
||||
return "Invalid float setting"
|
||||
end
|
||||
|
||||
min = tonumber(min)
|
||||
max = tonumber(max)
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = "float",
|
||||
default = default,
|
||||
min = min,
|
||||
max = max,
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if setting_type == "enum" then
|
||||
local default, values = remaining_line:match("^"
|
||||
-- first value (default) may be empty (i.e. is optional)
|
||||
.. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "(" .. CHAR_CLASSES.FLAGS .. "+)"
|
||||
.. "$")
|
||||
|
||||
if not default or values == "" then
|
||||
return "Invalid enum setting"
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = "enum",
|
||||
default = default,
|
||||
values = values:split(",", true),
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if setting_type == "path" or setting_type == "filepath" then
|
||||
local default = remaining_line:match("^(.*)$")
|
||||
|
||||
if not default then
|
||||
return "Invalid path setting"
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = setting_type,
|
||||
default = default,
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if setting_type == "flags" then
|
||||
local default, possible = remaining_line:match("^"
|
||||
-- first value (default) may be empty (i.e. is optional)
|
||||
-- this is implemented by making the last value optional, and
|
||||
-- swapping them around if it turns out empty.
|
||||
.. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*"
|
||||
.. "(" .. CHAR_CLASSES.FLAGS .. "*)"
|
||||
.. "$")
|
||||
|
||||
if not default or not possible then
|
||||
return "Invalid flags setting"
|
||||
end
|
||||
|
||||
if possible == "" then
|
||||
possible = default
|
||||
default = ""
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = name,
|
||||
readable_name = readable_name,
|
||||
type = "flags",
|
||||
default = default,
|
||||
possible = flags_to_table(possible),
|
||||
requires = requires,
|
||||
comment = comment,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
return "Invalid setting type \"" .. setting_type .. "\""
|
||||
end
|
||||
|
||||
local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
|
||||
-- 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)
|
||||
if error_msg then
|
||||
core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
|
||||
end
|
||||
line = file:read("*line")
|
||||
end
|
||||
|
||||
result.current_comment = nil
|
||||
result.current_hide_level = nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns table of setting types
|
||||
--
|
||||
-- @param read_all Whether to ignore certain setting types for GUI or not
|
||||
-- @parse_mods Whether to parse settingtypes.txt in mods and games
|
||||
function settingtypes.parse_config_file(read_all, parse_mods)
|
||||
local settings = {}
|
||||
|
||||
do
|
||||
local builtin_path = core.get_builtin_path() .. FILENAME
|
||||
local file = io.open(builtin_path, "r")
|
||||
if not file then
|
||||
core.log("error", "Can't load " .. FILENAME)
|
||||
return settings
|
||||
end
|
||||
|
||||
parse_single_file(file, builtin_path, read_all, settings, 0, true)
|
||||
|
||||
file:close()
|
||||
end
|
||||
|
||||
if parse_mods then
|
||||
-- Parse games
|
||||
local games_category_initialized = false
|
||||
for _, game in ipairs(pkgmgr.games) do
|
||||
local path = game.path .. DIR_DELIM .. FILENAME
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
if not games_category_initialized then
|
||||
fgettext_ne("Content: Games") -- not used, but needed for xgettext
|
||||
table.insert(settings, {
|
||||
name = "Content: Games",
|
||||
level = 0,
|
||||
type = "category",
|
||||
})
|
||||
games_category_initialized = true
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = game.path,
|
||||
readable_name = game.title,
|
||||
level = 1,
|
||||
type = "category",
|
||||
})
|
||||
|
||||
parse_single_file(file, path, read_all, settings, 2, false)
|
||||
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse mods
|
||||
pkgmgr.load_all()
|
||||
local mods_category_initialized = false
|
||||
local mods = pkgmgr.global_mods:get_list()
|
||||
table.sort(mods, function(a, b) return a.name < b.name end)
|
||||
|
||||
for _, mod in ipairs(mods) do
|
||||
local path = mod.path .. DIR_DELIM .. FILENAME
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
if not mods_category_initialized then
|
||||
fgettext_ne("Content: Mods") -- not used, but needed for xgettext
|
||||
table.insert(settings, {
|
||||
name = "Content: Mods",
|
||||
level = 0,
|
||||
type = "category",
|
||||
})
|
||||
mods_category_initialized = true
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = mod.path,
|
||||
readable_name = mod.title or mod.name,
|
||||
level = 1,
|
||||
type = "category",
|
||||
})
|
||||
|
||||
parse_single_file(file, path, read_all, settings, 2, false)
|
||||
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse client mods
|
||||
local clientmods_category_initialized = false
|
||||
local clientmods = {}
|
||||
pkgmgr.get_mods(core.get_clientmodpath(), "clientmods", clientmods)
|
||||
for _, mod in ipairs(clientmods) do
|
||||
local path = mod.path .. DIR_DELIM .. FILENAME
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
if not clientmods_category_initialized then
|
||||
fgettext_ne("Client Mods") -- not used, but needed for xgettext
|
||||
table.insert(settings, {
|
||||
name = "Client Mods",
|
||||
level = 0,
|
||||
type = "category",
|
||||
})
|
||||
clientmods_category_initialized = true
|
||||
end
|
||||
|
||||
table.insert(settings, {
|
||||
name = mod.path,
|
||||
readable_name = mod.title or mod.name,
|
||||
level = 1,
|
||||
type = "category",
|
||||
})
|
||||
|
||||
parse_single_file(file, path, read_all, settings, 2, false)
|
||||
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return settings
|
||||
end
|
|
@ -1,120 +0,0 @@
|
|||
--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.
|
||||
|
||||
|
||||
local shadow_levels_labels = {
|
||||
fgettext("Disabled"),
|
||||
fgettext("Very Low"),
|
||||
fgettext("Low"),
|
||||
fgettext("Medium"),
|
||||
fgettext("High"),
|
||||
fgettext("Very High"),
|
||||
fgettext("Custom"),
|
||||
}
|
||||
local PRESET_DISABLED = 1
|
||||
local PRESET_CUSTOM = #shadow_levels_labels
|
||||
|
||||
|
||||
-- max distance, texture size, texture_32bit, filters, map color
|
||||
local shadow_presets = {
|
||||
[2] = { 62, 512, true, 0, false },
|
||||
[3] = { 93, 1024, true, 0, false },
|
||||
[4] = { 140, 2048, true, 1, false },
|
||||
[5] = { 210, 4096, true, 2, true },
|
||||
[6] = { 300, 8192, true, 2, true },
|
||||
}
|
||||
|
||||
|
||||
local function detect_mapping_idx()
|
||||
if not core.settings:get_bool("enable_dynamic_shadows", false) then
|
||||
return PRESET_DISABLED
|
||||
end
|
||||
|
||||
local shadow_map_max_distance = tonumber(core.settings:get("shadow_map_max_distance"))
|
||||
local shadow_map_texture_size = tonumber(core.settings:get("shadow_map_texture_size"))
|
||||
local shadow_map_texture_32bit = core.settings:get_bool("shadow_map_texture_32bit", false)
|
||||
local shadow_filters = tonumber(core.settings:get("shadow_filters"))
|
||||
local shadow_map_color = core.settings:get_bool("shadow_map_color", false)
|
||||
|
||||
for i = 2, 6 do
|
||||
local preset = shadow_presets[i]
|
||||
if preset[1] == shadow_map_max_distance and
|
||||
preset[2] == shadow_map_texture_size and
|
||||
preset[3] == shadow_map_texture_32bit and
|
||||
preset[4] == shadow_filters and
|
||||
preset[5] == shadow_map_color then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return PRESET_CUSTOM
|
||||
end
|
||||
|
||||
|
||||
local function apply_preset(preset)
|
||||
if preset then
|
||||
core.settings:set_bool("enable_dynamic_shadows", true)
|
||||
core.settings:set("shadow_map_max_distance", preset[1])
|
||||
core.settings:set("shadow_map_texture_size", preset[2])
|
||||
core.settings:set_bool("shadow_map_texture_32bit", preset[3])
|
||||
core.settings:set("shadow_filters", preset[4])
|
||||
core.settings:set_bool("shadow_map_color", preset[5])
|
||||
else
|
||||
core.settings:set_bool("enable_dynamic_shadows", false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return {
|
||||
query_text = "Shadows",
|
||||
requires = {
|
||||
opengl = true,
|
||||
},
|
||||
get_formspec = function(self, avail_w)
|
||||
local labels = table.copy(shadow_levels_labels)
|
||||
local idx = detect_mapping_idx()
|
||||
|
||||
-- Remove "custom" if not already selected
|
||||
if idx ~= PRESET_CUSTOM then
|
||||
table.remove(labels, PRESET_CUSTOM)
|
||||
end
|
||||
|
||||
local fs =
|
||||
"label[0,0.2;" .. fgettext("Dynamic shadows") .. "]" ..
|
||||
"dropdown[0,0.4;3,0.8;dd_shadows;" .. table.concat(labels, ",") .. ";" .. idx .. ";true]" ..
|
||||
"label[0,1.5;" .. core.colorize("#bbb", fgettext("(The game will need to enable shadows as well)")) .. "]"
|
||||
return fs, 1.8
|
||||
end,
|
||||
on_submit = function(self, fields)
|
||||
if fields.dd_shadows then
|
||||
local old_shadow_level_idx = detect_mapping_idx()
|
||||
local shadow_level_idx = tonumber(fields.dd_shadows)
|
||||
if shadow_level_idx == nil or shadow_level_idx == old_shadow_level_idx then
|
||||
return false
|
||||
end
|
||||
|
||||
if shadow_level_idx == PRESET_CUSTOM then
|
||||
core.settings:set_bool("enable_dynamic_shadows", true)
|
||||
return true
|
||||
end
|
||||
|
||||
local preset = shadow_presets[shadow_level_idx]
|
||||
apply_preset(preset)
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue