player_settings/api.lua
2023-06-07 18:36:46 +08:00

262 lines
6.7 KiB
Lua

local WP = minetest.get_worldpath()
minetest.mkdir(WP .. "/player_settings/")
local _ps = player_settings
local s = {}
local s_nochg = {}
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.registered_on_settings_set = {}
_ps.register_on_settings_set = function(func)
table.insert(_ps.registered_on_settings_set,func)
end
_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
if not minetest.player_exists(name) then
return false, "PLAYER_NOT_EXIST"
end
minetest.safe_file_write(_ps.get_settings_path(name), minetest.serialize(tb))
return true
end
_ps.set_setting = function(name,key,value)
if type(name) == "userdata" then
name = name:get_player_name()
end
if not minetest.player_exists(name) then
return false, "PLAYER_NOT_EXIST"
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)
s_nochg[name] = 0
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
if setting_entry.number_min and setting_entry.number_min > value then
return false, "NUMBER_TOO_SMALL"
elseif setting_entry.number_max and setting_entry.number_max < value then
return false, "NUMBER_TOO_LARGE"
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
-- validator = function(name, key, value)
if setting_entry.validator then
local status, errmsg = setting_entry.validator(name, key, value)
if not status then
return false, (errmsg or "VALIDATION_FAILED")
end
end
minetest.log("action", string.format(
"[player_settings] %s set setting %s to %s",
name, key, value
))
local old_value = s[name][key]
if setting_entry.default and setting_entry.default == value then
value = nil -- to save disk space
end
s[name][key] = value
s_nochg[name] = nil
-- after_change = function(name, key, old_value, new_value) end,
if setting_entry.after_change then
setting_entry.after_change(name, key, old_value, value or setting_entry.default)
end
for _,func in ipairs(_ps.registered_on_settings_set) do
func(name, key, old_value, value or setting_entry.default)
end
return true
end
_ps.set_default = function(name,key)
if type(name) == "userdata" then
name = name:get_player_name()
end
if not minetest.player_exists(name) then
return false, "PLAYER_NOT_EXIST"
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)
s_nochg[name] = 0
end
local old_value = s[name][key]
minetest.log("action", string.format(
"[player_settings] %s set setting %s to default",
name, key
))
if setting_entry.after_change then
setting_entry.after_change(name, key, old_value, setting_entry.default)
end
for _,func in ipairs(_ps.registered_on_settings_set) do
func(name, key, old_value, setting_entry.default)
end
s[name][key] = nil
s_nochg[name] = nil
end
_ps.get_setting = function(name,key)
if type(name) == "userdata" then
name = name:get_player_name()
end
if not minetest.player_exists(name) then
return false, "PLAYER_NOT_EXIST"
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)
s_nochg[name] = 0
end
local value = s[name][key]
if value == nil then
value = setting_entry.default
end
return true, value
end
_ps.erase_settings = function(name)
if type(name) == "userdata" then
name = name:get_player_name()
end
minetest.log("action", string.format(
"[player_settings] Erasing %s's data",
name
))
s[name] = nil
s_nochg[name] = nil
os.remove(_ps.get_settings_path(name))
end
_ps.save_all_settings = function()
for k,v in pairs(s) do
if not s_nochg[k] then
minetest.log("action", string.format(
"[player_settings] Writing %s's data",
k
))
_ps.write_settings(k, v)
s_nochg[k] = 1
else
if s_nochg[k] > 2 then
minetest.log("action", string.format(
"[player_settings] %s's data has been idle for 120 seconds. Removing from cache.",
k
))
s[k] = nil -- Free memory
s_nochg[k] = nil
else
minetest.log("action", string.format(
"[player_settings] %s's data has not been edited. Skipped.",
k
))
s_nochg[k] = s_nochg[k] + 1
end
end
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)
do
local old_remove_player = minetest.remove_player
function minetest.remove_player(name)
local success = old_remove_player(name)
if success == 0 then
_ps.erase_settings(name)
end
return success
end
end