local concat_path = modlib.file.concat_path local auth_handler = minetest.get_auth_handler() epidermis.paths = {dynamic_textures = {}} for _, folder in pairs{"skindb", "epidermi"} do local path = modlib.file.concat_path({ minetest.get_worldpath(), "data", "epidermis", "textures", folder }) minetest.mkdir(path) epidermis.paths.dynamic_textures[folder] = path end epidermis.paths.playerdata = modlib.file.concat_path({ minetest.get_worldpath(), "data", "epidermis", "players" }) minetest.mkdir(epidermis.paths.playerdata) function epidermis.get_player_data(playername) local filepath = concat_path{epidermis.paths.playerdata, playername .. ".lua"} local content = modlib.file.read(filepath) if not content then return end local playerdata = assert(modlib.luon:read_string(content)) return playerdata end function epidermis.set_player_data(playername, data) local filepath = concat_path{epidermis.paths.playerdata, playername .. ".lua"} assert(modlib.file.write(filepath, modlib.luon:write_string(data))) end local function player_exists(name) if name == "singleplayer" and minetest.is_singleplayer() then return true end return auth_handler.get_auth(name) ~= nil end -- Remove unused player data & mark used textures local used_textures = {} for _, filename in ipairs(minetest.get_dir_list(epidermis.paths.playerdata, false)) do local playername = filename:match"^(.-)%.lua$" if playername then local playerdata = epidermis.get_player_data(playername) used_textures[playerdata.epidermis] = true end end -- HACK the auth handler returns nil at load time (see https://github.com/minetest/minetest/issues/11956) minetest.after(0, function() for _, filename in ipairs(minetest.get_dir_list(epidermis.paths.playerdata, false)) do local playername = filename:match"^(.-)%.lua$" if playername and not player_exists(playername) then local playerdata = epidermis.get_player_data(playername) used_textures[playerdata.epidermis] = true assert(os.remove(concat_path{epidermis.paths.playerdata, playername .. ".lua"})) end end end) -- Remove unused textures & store highest texture ID local epidermi_texture_path = epidermis.paths.dynamic_textures.epidermi for _, dirname in ipairs(minetest.get_dir_list(epidermi_texture_path, true)) do local dir_path = concat_path{epidermi_texture_path, dirname} local highest_number local last_filename local used = false local function remove_if_unused(filename) if used_textures[filename] then used = true else assert(os.remove(concat_path{dir_path, filename})) end end local filenames = minetest.get_dir_list(dir_path, false) local delete = modlib.table.contains(filenames, "delete") if delete then -- Move deletion marker to end through a swap so that it is deleted last filenames[#filenames], filenames[delete] = filenames[delete], filenames[#filenames] end for _, filename in ipairs(filenames) do if delete then remove_if_unused(filename) else local number = filename:match("^" .. modlib.text.escape_magic_chars(dirname) .. "_(%d+)%.png$") if number then number = tonumber(number) if last_filename then if number > highest_number then remove_if_unused(last_filename) highest_number = number last_filename = filename else remove_if_unused(filename) end else highest_number = number last_filename = filename end end end end if not used and (delete or #filenames == 0) then assert(os.remove(dir_path)) end end function epidermis.get_epidermis_path(paintable_id, texture_id) local texture_name = ("epidermis_paintable_%d_%d.png"):format(paintable_id, texture_id) local path = concat_path{ epidermi_texture_path, ("epidermis_paintable_%d"):format(paintable_id), texture_name } return path, texture_name end function epidermis.get_epidermis_path_from_texture(dynamic_texture) local tex_name, dir_name = dynamic_texture:match"^((epidermis_paintable_%d+)_%d+.png)$" if not (tex_name and dir_name) then return end return modlib.file.concat_path{epidermi_texture_path, dir_name, tex_name} end function epidermis.get_last_epidermis_path(paintable_id) local dir_name = ("epidermis_paintable_%d"):format(paintable_id) local max_tex_id = -math.huge for _, filename in ipairs(minetest.get_dir_list(concat_path{epidermi_texture_path, dir_name}, false)) do local number = filename:match("^" .. modlib.text.escape_magic_chars(dir_name) .. "_(%d+)%.png$") if number then max_tex_id = math.max(max_tex_id, tonumber(number)) end end if max_tex_id == -math.huge then return end return epidermis.get_epidermis_path(paintable_id, max_tex_id) end function epidermis.write_epidermis(paintable_id, texture_id, raw_png_data) local path, texture_name = epidermis.get_epidermis_path(paintable_id, texture_id) assert(modlib.file.write_binary(path, raw_png_data)) return path, texture_name end function epidermis.mark_for_deletion(paintable_id) assert(modlib.file.write_unsafe(concat_path{ epidermi_texture_path, ("epidermis_paintable_%d"):format(paintable_id), "delete" }, "")) end -- SkinDB function epidermis.write_skindb_skin(id, raw_png_data, meta_data) local texture_name = ("epidermis_skindb_%d.png"):format(id) local path = concat_path{ epidermis.paths.dynamic_textures.skindb, texture_name } assert(modlib.file.write_binary(path, raw_png_data)) assert(modlib.file.write(concat_path{ epidermis.paths.dynamic_textures.skindb, texture_name .. ".json" }, modlib.json:write_string(meta_data))) return path, texture_name end function epidermis.remove_skindb_skin(id) local texture_name = ("epidermis_skindb_%d.png"):format(id) local path = concat_path{ epidermis.paths.dynamic_textures.skindb, texture_name } assert(os.remove(path)) assert(os.remove(path .. ".json")) end -- Player-set epidermis persistence minetest.register_on_joinplayer(function(player) local data = epidermis.get_player_data(player:get_player_name()) if data then epidermis.dynamic_add_media(assert(epidermis.get_epidermis_path_from_texture(data.epidermis)), function() epidermis.set_skin(player, data.epidermis) end, true) end end)