/ api.lua
api.lua
  1  local WP = minetest.get_worldpath()
  2  minetest.mkdir(WP .. "/player_settings/")
  3  
  4  local _ps = player_settings
  5  local s = {}
  6  local s_nochg = {}
  7  
  8  local function RTN_TRUE() return true end
  9  
 10  local function do_register()
 11  	local tb = {}
 12  	local function reg_func(name, def)
 13  		if not def.allow_show then
 14  			def.allow_show = RTN_TRUE
 15  		end
 16  		def.name = name
 17  		tb[name] = def
 18  	end
 19  	local function unreg_func(name)
 20  		tb[name] = nil
 21  	end
 22  	return tb, reg_func, unreg_func
 23  end
 24  
 25  _ps.registered_metacategories, _ps.register_metacategory, _ps.unregister_metacategory = do_register()
 26  _ps.registered_categories, _ps.register_category, _ps.unregister_category = do_register()
 27  _ps.registered_settings, _ps.register_setting, _ps.unregister_setting = do_register()
 28  
 29  _ps.registered_on_settings_set = {}
 30  _ps.register_on_settings_set = function(func)
 31  	table.insert(_ps.registered_on_settings_set,func)
 32  end
 33  
 34  _ps.get_settings_path = function(name)
 35  	return (WP .. "/player_settings/" .. name .. ".conf.lua")
 36  end
 37  
 38  _ps.get_settings = function(name)
 39  	if type(name) == "userdata" then
 40  		name = name:get_player_name()
 41  	end
 42  	local fp = _ps.get_settings_path(name)
 43  	local f = io.open(fp, "r")
 44  	if not f then return {} end
 45  	local contents = f:read("*a")
 46  	return minetest.deserialize(contents,true)
 47  end
 48  
 49  _ps.write_settings = function(name, tb)
 50  	if type(name) == "userdata" then
 51  		name = name:get_player_name()
 52  	end
 53  	if not minetest.player_exists(name) then
 54  		return false, "PLAYER_NOT_EXIST"
 55  	end
 56  	minetest.safe_file_write(_ps.get_settings_path(name), minetest.serialize(tb))
 57  	return true
 58  end
 59  
 60  _ps.set_setting = function(name,key,value)
 61  	if type(name) == "userdata" then
 62  		name = name:get_player_name()
 63  	end
 64  	if not minetest.player_exists(name) then
 65  		return false, "PLAYER_NOT_EXIST"
 66  	end
 67  	local setting_entry = _ps.registered_settings[key]
 68  	if not setting_entry then
 69  		return false, "KEY_NOT_EXIST"
 70  	end
 71  	if not s[name] then
 72  		s[name] = _ps.get_settings(name)
 73  		s_nochg[name] = 0
 74  	end
 75  	-- type = "int" / "string" / "bool" / "float" / "enum",
 76  	if setting_entry.type == "int" or setting_entry.type == "float" then
 77  		value = tonumber(value)
 78  		if not value then
 79  			return false, "TYPE_CONVERT_FAILED"
 80  		end
 81  		if setting_entry.type == "int" then
 82  			value = math.floor(value + 0.5)
 83  		end
 84  		if setting_entry.number_min and setting_entry.number_min > value then
 85  			return false, "NUMBER_TOO_SMALL"
 86  		elseif setting_entry.number_max and setting_entry.number_max < value then
 87  			return false, "NUMBER_TOO_LARGE"
 88  		end
 89  
 90  	elseif setting_entry.type == "string" then
 91  		value = tostring(value)
 92  	elseif setting_entry.type == "enum" then
 93  		-- enum_type = "int" / "string" / "float",
 94  		if setting_entry.enum_type == "int" or setting_entry.enum_type == "float" then
 95  			value = tonumber(value)
 96  			if not value then
 97  				return false, "TYPE_CONVERT_FAILED"
 98  			end
 99  			if setting_entry.enum_type == "int" then
100  				value = math.floor(value + 0.5)
101  			end
102  		elseif setting_entry.enum_type == "string" then
103  			value = tostring(value)
104  		else
105  			return false, "SETTING_ENUM_TYPE_INVALID"
106  		end
107  		if not (function()
108  			for _,y in ipairs(setting_entry.enum_choices) do
109  				if value == y then return true end
110  			end
111  			return false
112  		end)() then
113  			return false, "SETTING_VALUE_NOT_IN_ENUM"
114  		end
115  	elseif setting_entry.type == "bool" then
116  		if value == true or value == "true" or (tonumber(value) or 0) > 0 then
117  			value = true
118  		else
119  			value = false
120  		end
121  	else
122  		return false, "SETTING_TYPE_INVALID"
123  	end
124  	-- validator = function(name, key, value)
125  	if setting_entry.validator then
126  		local status, errmsg = setting_entry.validator(name, key, value)
127  		if not status then
128  			return false, (errmsg or "VALIDATION_FAILED")
129  		end
130  	end
131  	minetest.log("action", string.format(
132  		"[player_settings] %s set setting %s to %s",
133  		name, key, value
134  	))
135  	local old_value = s[name][key]
136  	if setting_entry.default and setting_entry.default == value then
137  		value = nil -- to save disk space
138  	end
139  	s[name][key] = value
140  	s_nochg[name] = nil
141  	-- after_change = function(name, key, old_value, new_value) end,
142  	if setting_entry.after_change then
143  		setting_entry.after_change(name, key, old_value, value or setting_entry.default)
144  	end
145  	for _,func in ipairs(_ps.registered_on_settings_set) do
146  		func(name, key, old_value, value or setting_entry.default)
147  	end
148  	return true
149  end
150  
151  _ps.set_default = function(name,key)
152  	if type(name) == "userdata" then
153  		name = name:get_player_name()
154  	end
155  	if not minetest.player_exists(name) then
156  		return false, "PLAYER_NOT_EXIST"
157  	end
158  	local setting_entry = _ps.registered_settings[key]
159  	if not setting_entry then
160  		return false, "KEY_NOT_EXIST"
161  	end
162  	if not s[name] then
163  		s[name] = _ps.get_settings(name)
164  		s_nochg[name] = 0
165  	end
166  	local old_value = s[name][key]
167  	minetest.log("action", string.format(
168  		"[player_settings] %s set setting %s to default",
169  		name, key
170  	))
171  	if setting_entry.after_change then
172  		setting_entry.after_change(name, key, old_value, setting_entry.default)
173  	end
174  	for _,func in ipairs(_ps.registered_on_settings_set) do
175  		func(name, key, old_value, setting_entry.default)
176  	end
177  	s[name][key] = nil
178  	s_nochg[name] = nil
179  end
180  
181  _ps.get_setting = function(name,key)
182  	if type(name) == "userdata" then
183  		name = name:get_player_name()
184  	end
185  	if not minetest.player_exists(name) then
186  		return false, "PLAYER_NOT_EXIST"
187  	end
188  	local setting_entry = _ps.registered_settings[key]
189  	if not setting_entry then
190  		return false, "KEY_NOT_EXIST"
191  	end
192  	if not s[name] then
193  		s[name] = _ps.get_settings(name)
194  		s_nochg[name] = 0
195  	end
196  	local value = s[name][key]
197  	if value == nil then
198  		value = setting_entry.default
199  	end
200  	return true, value
201  end
202  
203  _ps.erase_settings = function(name)
204  	if type(name) == "userdata" then
205  		name = name:get_player_name()
206  	end
207  	minetest.log("action", string.format(
208  		"[player_settings] Erasing %s's data",
209  		name
210  	))
211  	s[name] = nil
212  	s_nochg[name] = nil
213  	os.remove(_ps.get_settings_path(name))
214  end
215  
216  _ps.save_all_settings = function()
217  	for k,v in pairs(s) do
218  		if not s_nochg[k] then
219  			minetest.log("action", string.format(
220  				"[player_settings] Writing %s's data",
221  				k
222  			))
223  			_ps.write_settings(k, v)
224  			s_nochg[k] = 1
225  		else
226  			if s_nochg[k] > 2 then
227  				minetest.log("action", string.format(
228  					"[player_settings] %s's data has been idle for 120 seconds. Removing from cache.",
229  					k
230  				))
231  				s[k] = nil -- Free memory
232  				s_nochg[k] = nil
233  			else
234  				minetest.log("action", string.format(
235  					"[player_settings] %s's data has not been edited. Skipped.",
236  					k
237  				))
238  				s_nochg[k] = s_nochg[k] + 1
239  			end
240  		end
241  	end
242  end
243  
244  local function after_loop()
245  	_ps.save_all_settings()
246  	minetest.after(60, after_loop)
247  end
248  
249  minetest.after(5, after_loop)
250  minetest.register_on_shutdown(_ps.save_all_settings)
251  
252  do
253  	local old_remove_player = minetest.remove_player
254  	function minetest.remove_player(name)
255  		local success = old_remove_player(name)
256  		if success == 0 then
257  			_ps.erase_settings(name)
258  		end
259  		return success
260  	end
261  end