This commit is contained in:
1F616EMO 2023-06-05 21:30:08 +08:00
parent 0df68c2435
commit 0290cd0df5
No known key found for this signature in database
GPG Key ID: CDF659A4657D3557
12 changed files with 597 additions and 2 deletions

51
.luacheckrc Normal file
View File

@ -0,0 +1,51 @@
read_globals = {
"DIR_DELIM", "INIT",
"minetest", "core",
"dump", "dump2",
"Raycast",
"Settings",
"PseudoRandom",
"PerlinNoise",
"VoxelManip",
"SecureRandom",
"VoxelArea",
"PerlinNoiseMap",
"PcgRandom",
"ItemStack",
"AreaStore",
"vector",
"flow",
table = {
fields = {
"copy",
"indexof",
"insert_all",
"key_value_swap",
"shuffle",
}
},
string = {
fields = {
"split",
"trim",
}
},
math = {
fields = {
"hypot",
"sign",
"factorial"
}
},
}
globals = {
"player_settings",
}

8
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"Lua.diagnostics.globals": [
"minetest",
"flow",
"player_settings",
"dump"
]
}

10
LICENSE
View File

@ -1,6 +1,9 @@
License of code
==================
MIT License
Copyright (c) 2023 C&C Minetest Server
Copyright (c) 2023 1F616EMO
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,3 +22,8 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
License of Media
==================
* textures/settings_*: by Zughy, under CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/

View File

@ -1 +1,90 @@
# settings
# player_settings
## Definition tables
### Metacategories
Used by `player_settings.register_metacategory`.
```lua
{
title = "",
-- Display title of the metacategory.
allow_show = function(player) return true end,
-- Determine if the metacategory should be shown to the player.
-- If returned false, all its child will be hidden.
}
```
### Categories
Used by `player_settings.register_category`.
```lua
{
title = "",
-- Display title of the category.
metacategory = "general",
-- The ID of the metacategory.
allow_show = function(player) return true end,
-- Determine if the category should be shown to the player.
-- If returned false, all its child will be hidden.
}
```
### Settings
Used by `player_settings.register_setting`.
```lua
{
description = "",
-- Short description of the setting. It should be as short and as simple as possible.
long_description = ""
-- Long description of the setting.
type = "int" / "string" / "bool" / "float" / "enum",
-- Type of the setting.
default = "",
-- Default value of the setting.
number_min = math.min,
number_max = math.huge,
-- Only applies when type == "int" / "float".
-- The lowest and the highest value of the setting.
enum_type = "int" / "string" / "float",
-- Only applies when type == "enum".
-- The type of the choices.
enum_choices = ["1", "2", ...]
-- Only applies when type == "enum".
-- All the avaliable choices.
validator = function(name, key, value) end,
-- Validates the input before it is being stored.
-- `name` is the player's name.
-- `key` is the ID of the setting.
-- `value` is the value of the setting to be modified.
-- Should return either `true` or a error message.
after_change = function(name, key, old_value, new_value) end,
-- Function triggered after a player had successfully modified one setting.
-- `name` is the player's name.
-- `key` is the ID of the setting.
-- `old_value` is the value of the setting before modifications.
-- `new_value` is the value of the setting after modifications.
category = "general",
-- The ID of the category.
allow_show = function(name) return true end,
-- Determine if the setting should be shown to the player.
-- If returned false, it will be hidden.
}
```

152
api.lua Normal file
View File

@ -0,0 +1,152 @@
local WP = minetest.get_worldpath()
minetest.mkdir(WP .. "/player_settings/")
local _ps = player_settings
local s = {}
local function RTN_TRUE() return true end
local function do_register()
local tb = {}
local function reg_func(name, def)
if not def.allow_show then
def.allow_show = RTN_TRUE
end
def.name = name
tb[name] = def
end
local function unreg_func(name)
tb[name] = nil
end
return tb, reg_func, unreg_func
end
_ps.registered_metacategories, _ps.register_metacategory, _ps.unregister_metacategory = do_register()
_ps.registered_categories, _ps.register_category, _ps.unregister_category = do_register()
_ps.registered_settings, _ps.register_setting, _ps.unregister_setting = do_register()
_ps.get_settings_path = function(name)
return (WP .. "/player_settings/" .. name .. ".conf.lua")
end
_ps.get_settings = function(name)
if type(name) == "userdata" then
name = name:get_player_name()
end
local fp = _ps.get_settings_path(name)
local f = io.open(fp, "r")
if not f then return {} end
local contents = f:read("*a")
return minetest.deserialize(contents,true)
end
_ps.write_settings = function(name, tb)
if type(name) == "userdata" then
name = name:get_player_name()
end
minetest.safe_file_write(_ps.get_settings_path(name), minetest.serialize(tb))
end
_ps.set_setting = function(name,key,value)
if type(name) == "userdata" then
name = name:get_player_name()
end
local setting_entry = _ps.registered_settings[key]
if not setting_entry then
return false, "KEY_NOT_EXIST"
end
if not s[name] then
s[name] = _ps.get_settings(name)
end
-- type = "int" / "string" / "bool" / "float" / "enum",
if setting_entry.type == "int" or setting_entry.type == "float" then
value = tonumber(value)
if not value then
return false, "TYPE_CONVERT_FAILED"
end
if setting_entry.type == "int" then
value = math.floor(value + 0.5)
end
elseif setting_entry.type == "string" then
value = tostring(value)
elseif setting_entry.type == "enum" then
-- enum_type = "int" / "string" / "float",
if setting_entry.enum_type == "int" or setting_entry.enum_type == "float" then
value = tonumber(value)
if not value then
return false, "TYPE_CONVERT_FAILED"
end
if setting_entry.enum_type == "int" then
value = math.floor(value + 0.5)
end
elseif setting_entry.enum_type == "string" then
value = tostring(value)
else
return false, "SETTING_ENUM_TYPE_INVALID"
end
if not (function()
for _,y in ipairs(setting_entry.enum_choices) do
if value == y then return true end
end
return false
end)() then
return false, "SETTING_VALUE_NOT_IN_ENUM"
end
elseif setting_entry.type == "bool" then
if value == true or value == "true" or (tonumber(value) or 0) > 0 then
value = true
else
value = false
end
else
return false, "SETTING_TYPE_INVALID"
end
s[name][key] = value
return true
end
_ps.set_default = function(name,key)
if type(name) == "userdata" then
name = name:get_player_name()
end
local setting_entry = _ps.registered_settings[key]
if not setting_entry then
return false, "KEY_NOT_EXIST"
end
if not s[name] then
s[name] = _ps.get_settings(name)
end
s[name][key] = nil
end
_ps.get_setting = function(name,key)
if type(name) == "userdata" then
name = name:get_player_name()
end
local setting_entry = _ps.registered_settings[key]
if not setting_entry then
return false, "KEY_NOT_EXIST"
end
if not s[name] then
s[name] = _ps.get_settings(name)
end
local value = s[name][key]
if value == nil then
value = setting_entry.default
end
return value
end
_ps.save_all_settings = function()
for k,v in pairs(s) do
_ps.write_settings(k, v)
end
end
local after_loop = function()
_ps.save_all_settings()
minetest.after(60,_ps.save_all_settings)
end
minetest.after(5, after_loop)
minetest.register_on_shutdown(_ps.save_all_settings)

43
example.lua Normal file
View File

@ -0,0 +1,43 @@
local _ps = player_settings
local S = minetest.get_translator("player_settings")
_ps.register_metacategory("ps_example_mc",{
title = S("Settings Examples MC"),
})
_ps.register_category("ps_example",{
title = S("Settings Examples"),
metacategory = "ps_example_mc"
})
_ps.register_setting("ps_example_int", {
type = "int",
description = S("Example of @1", "int"),
long_description = S("Long description. \nExample of @1","int"),
default = 1,
category = "ps_example",
})
_ps.register_setting("ps_example_float", {
type = "float",
description = S("Example of @1", "float"),
long_description = S("Long description. \nExample of @1","float"),
default = 1.2345,
category = "ps_example",
})
_ps.register_setting("ps_example_string", {
type = "string",
description = S("Example of @1", "string"),
long_description = S("Long description. \nExample of @1","string"),
default = "DEFAULT",
category = "ps_example",
})
_ps.register_setting("ps_example_bool", {
type = "bool",
description = S("Example of @1", "bool"),
long_description = S("Long description. \nExample of @1","bool"),
default = true,
category = "ps_example",
})

222
gui.lua Normal file
View File

@ -0,0 +1,222 @@
local gui = flow.widgets
local _ps = player_settings
local S = minetest.get_translator("player_settings")
_ps.gui = flow.make_gui(function(player,ctx)
ctx.name = player:get_player_name()
if not ctx.navbarData then
local settings_by_category = {}
for k,v in pairs(_ps.registered_settings) do
if v.allow_show(ctx.name) then
if not settings_by_category[v.category] then
settings_by_category[v.category] = {}
end
settings_by_category[v.category][k] = v
end
end
local categories_by_metacat = {}
for k,v in pairs(settings_by_category) do
if not (_ps.registered_categories[k] and _ps.registered_categories[k].allow_show(ctx.name)) then
settings_by_category[k] = nil
end
if not categories_by_metacat[_ps.registered_categories[k].metacategory] then
categories_by_metacat[_ps.registered_categories[k].metacategory] = {}
end
categories_by_metacat[_ps.registered_categories[k].metacategory][k] = v
end
for k,v in pairs(categories_by_metacat) do
if not (_ps.registered_metacategories[k] and _ps.registered_metacategories[k].allow_show(ctx.name)) then
categories_by_metacat[k] = nil
end
end
ctx.navbarData = categories_by_metacat
print(dump(ctx.navbarData))
end
local navbar = {}
for k,v in pairs(ctx.navbarData) do
table.insert(navbar,gui.Label{ label = _ps.registered_metacategories[k].title })
for k2,v2 in pairs(v) do
table.insert(navbar,gui.Button {
label = _ps.registered_categories[k2].title,
w = 1, expand = true,
on_event = function(player,ctx)
ctx.current_metacat = k
ctx.current_category = k2
return true
end,
})
end
end
navbar.w = 4; navbar.h = 10;
navbar.name = "shbox"
local settings_screen
if not ctx.current_category then
settings_screen = gui.Label {
w = 10, h = 10,
label = S("Select a category on the left."),
expand = true, align_h = "centre", align_w = "centre",
}
else
local list_settings = ctx.navbarData[ctx.current_metacat][ctx.current_category]
local svbox = {}
for k,v in pairs(list_settings) do
if v.type == "bool" then
table.insert(svbox, gui.HBox {
gui.Checkbox {
w = 5,h=1,
name = "settings_" .. k,
label = v.description,
selected = _ps.get_setting(ctx.name,k),
on_event = function(player,ctx)
local form = ctx.form
if type(form["settings_" .. k]) == "boolean" then
local status, errmsg =_ps.set_setting(ctx.name,k,form["settings_" .. k])
if not status then
print(errmsg)
form["settings_" .. k] = _ps.get_setting(ctx.name,k)
return true
end
end
end,
expand = true, align_h = "left",
},
gui.ImageButton {
w = 1, h = 1,
texture_name = "settings_reset.png",
name = "settingsReset_" .. k,
on_event = function(player,ctx)
local form = ctx.form
_ps.set_default(ctx.name,k)
form["settings_" .. k] = v.default
return true
end
},
gui.Image {
w = 1, h = 1,
name = "settingsInfo_" .. k,
texture_name = "settings_info.png",
},
gui.Tooltip {
gui_element_name = "settingsInfo_" .. k,
tooltip_text = v.long_description or ""
}
})
elseif v.type == "enum" then
table.insert(svbox, gui.HBox {
gui.Dropdown {
w = 3,h=1,
name = "settings_" .. k,
label = v.short_description,
items = v.enum_choices,
selected = _ps.util.idx_in_table(v.enum_choices,_ps.get_setting(ctx.name,k)),
},
gui.Button {
w = 2,h=1,
name = "settingsSubmit_" .. k,
label = S("Set"),
on_event = function(player,ctx)
local form = ctx.form
local status, errmsg =_ps.set_setting(ctx.name,k,v.enum_choices[form["settings_" .. k]])
if not status then
print(errmsg)
form["settings_" .. k] = _ps.util.idx_in_table(v.enum_choices,_ps.get_setting(ctx.name,k))
return true
end
end,
expand = true, align_h = "left",
},
gui.ImageButton {
w = 1, h = 1,
texture_name = "settings_reset.png",
name = "settingsReset_" .. k,
on_event = function(player,ctx)
local form = ctx.form
_ps.set_default(ctx.name,k)
form["settings_" .. k] = _ps.util.idx_in_table(v.enum_choices,v.default)
return true
end
},
gui.Image {
w = 1, h = 1,
name = "settingsInfo_" .. k,
texture_name = "settings_info.png",
},
gui.Tooltip {
gui_element_name = "settingsInfo_" .. k,
tooltip_text = v.long_description or ""
}
})
else -- String-like
table.insert(svbox, gui.Label {
label = v.description
})
print(_ps.get_setting(ctx.name,k))
table.insert(svbox, gui.HBox {
gui.Field {
w = 3,h=1,
name = "settings_" .. k,
default = _ps.get_setting(ctx.name,k),
},
gui.Button {
w = 2,h=1,
name = "settingsSubmit_" .. k,
label = S("Set"),
on_event = function(player,ctx)
local form = ctx.form
local status, errmsg =_ps.set_setting(ctx.name,k,form["settings_" .. k])
if not status then
print(errmsg)
form["settings_" .. k] = _ps.get_setting(ctx.name,k)
return true
end
end,
expand = true, align_h = "left",
},
gui.ImageButton {
w = 1, h = 1,
texture_name = "settings_reset.png",
name = "settingsReset_" .. k,
on_event = function(player,ctx)
local form = ctx.form
_ps.set_default(ctx.name,k)
form["settings_" .. k] = v.default
return true
end
},
gui.Image {
w = 1, h = 1,
name = "settingsInfo_" .. k,
texture_name = "settings_info.png",
},
gui.Tooltip {
gui_element_name = "settingsInfo_" .. k,
tooltip_text = v.long_description or ""
}
})
end
end
svbox.w = 10; svbox.h = 10;
svbox.name = "svbox"
settings_screen = gui.ScrollableVBox(svbox)
end
local rtn_gui = gui.VBox {
gui.HBox {
gui.Label { label = S("Settings"), expand = true, align_h = "left" },
gui.ButtonExit { w = 0.7, h = 0.7, label = "x" }
},
gui.Box{w = 1, h = 0.05, color = "grey", padding = 0},
gui.HBox {
gui.ScrollableVBox(navbar),
settings_screen
}
}
return rtn_gui
end)
minetest.register_chatcommand("settings", {
description = S("Open settings menu"),
func = function(name,param)
_ps.gui:show(minetest.get_player_by_name(name))
end
})

9
init.lua Normal file
View File

@ -0,0 +1,9 @@
local MP = minetest.get_modpath("player_settings")
player_settings = {}
dofile(MP .. "/util.lua")
dofile(MP .. "/api.lua")
dofile(MP .. "/gui.lua")
dofile(MP .. "/example.lua")

4
mod.conf Normal file
View File

@ -0,0 +1,4 @@
name = player_settings
title = Player Settings
description = Player-specific settings
depends = flow

BIN
textures/settings_info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

BIN
textures/settings_reset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

9
util.lua Normal file
View File

@ -0,0 +1,9 @@
local _ps = player_settings
_ps.util = {}
_ps.util.idx_in_table = function(tb,v)
for i,tv in ipairs(tb) do
if tv == v then return i end
end
return nil
end