-- Simple Skins mod for minetest -- Adds a simple skin selector to the inventory by using -- the default sfinv or inventory_plus when running. -- Released by TenPlus1 and based on Zeg9's code under MIT license -- check for minetest 5.x compatibility local is_54 = minetest.has_feature("direct_velocity_on_players") or nil local is_50 = minetest.has_feature("object_use_texture_alpha") or nil -- Load support for translation. local S if minetest.get_translator ~= nil then S = minetest.get_translator("skins") else if minetest.get_modpath("intllib") then dofile(minetest.get_modpath("intllib").."/init.lua") if intllib.make_gettext_pair then gettext, ngettext = intllib.make_gettext_pair() -- new gettext method else gettext = intllib.Getter() -- old text file method end S = gettext else -- boilerplate function S = function(str, ...) local args = {...} return str:gsub("@%d", function(match) return args[tonumber(match:sub(2))] end) end end end skins = { skins = {}, list = {}, meta = {}, formspec = {}, modpath = minetest.get_modpath("skins"), invplus = minetest.get_modpath("inventory_plus"), unified_inventory = minetest.get_modpath("unified_inventory"), default_skin_tab = false, sfinv = minetest.get_modpath("sfinv"), transparant_list = false, id = 1, file = minetest.get_worldpath() .. "/skins.mt", preview = minetest.settings:get_bool("skins_preview") or true, translate = S, skin_limit = tonumber(minetest.settings:get("skins_limit")) or 160 } -- check and use specific inventory if skins.unified_inventory then skins.transparant_list = true dofile(skins.modpath .. "/unified_inventory.lua") elseif skins.invplus then skins.transparant_list = true dofile(skins.modpath .. "/inventory_plus.lua") elseif skins.sfinv then skins.default_skin_tab = not skins.invplus and not skins.unified_inventory dofile(skins.modpath .. "/sfinv.lua") end -- load skin list and metadata local f, data, skin = 1 while skins.id <= skins.skin_limit do skin = "character_" .. skins.id -- does skin file exist ? f = io.open(skins.modpath .. "/textures/" .. skin .. ".png") -- escape loop if not found and remove last entry if not f then skins.list[skins.id] = nil skins.id = skins.id - 1 break end f:close() table.insert(skins.list, skin) -- does metadata exist for that skin file ? f = io.open(skins.modpath .. "/meta/" .. skin .. ".txt") if f then data = minetest.deserialize("return {" .. f:read("*all") .. "}") f:close() end -- add metadata to list skins.meta[skin] = { name = data and data.name and data.name:gsub("[%p%c]", "") or "", author = data and data.author and data.author:gsub("[%p%c]", "") or "" } skins.id = skins.id + 1 end -- load player skins file for backwards compatibility local input = io.open(skins.file, "r") local data = nil if input then data = input:read("*all") io.close(input) end if data and data ~= "" then local lines = string.split(data, "\n") for _, line in pairs(lines) do data = string.split(line, " ", 2) skins.skins[data[1]] = data[2] end end -- create formspec for skin selection page skins.formspec.main = function(name) local formspec = "label[.5,2;" .. S("Select Player Skin:") .. "]" .. "textlist[.5,2.5;6.8,6;skins_set;" local meta local selected = 1 for i = 1, #skins.list do formspec = formspec .. skins.meta[ skins.list[i] ].name if skins.skins[name] == skins.list[i] then selected = i meta = skins.meta[ skins.skins[name] ] end if i < #skins.list then formspec = formspec .."," end end if skins.transparant_list then formspec = formspec .. ";" .. selected .. ";true]" else formspec = formspec .. ";" .. selected .. ";false]" end if meta then if meta.name then formspec = formspec .. "label[2,.5;" .. S("Name: ") .. meta.name .. "]" end if meta.author then formspec = formspec .. "label[2,1;" .. S("Author: ") .. meta.author .. "]" end end -- if preview enabled then add player model to formspec (5.4dev only) if skins.preview == true then info = minetest.get_player_information(name) if info then local test = info.version_string or "5" if test:find("0.4.") or test:find("4.0.") or test:find("5.0.") or test:find("5.1.") or test:find("5.2.") or test:find("5.3.") then is_54 = false else is_54 = true end end if is_54 then formspec = formspec .. "model[6,-0.2;1.5,3;player;character50.b3d;" .. skins.skins[name] .. ".png;0,180;false;true]" else local head = "[combine:8x8:-8,-8="..skins.skins[name]..".png" local body = "[combine:8x12:-20,-20="..skins.skins[name]..".png" local left_leg = "[combine:4x12:-4,-20="..skins.skins[name]..".png" local right_leg = "[combine:4x12:-4,-20="..skins.skins[name]..".png^[transformFX" local left_hand = "[combine:4x12:-44,-20="..skins.skins[name]..".png" local right_hand = "[combine:4x12:-44,-20="..skins.skins[name]..".png^[transformFX" local back_head = "[combine:8x8:-24,-8="..skins.skins[name]..".png" local body_back = "[combine:8x12:-32,-20="..skins.skins[name]..".png" local legs_back = "[combine:4x12:-12,-20="..skins.skins[name]..".png" local left_leg_back = "[combine:4x12:-12,-20="..skins.skins[name]..".png" local right_leg_back = "[combine:4x12:-12,-20="..skins.skins[name]..".png^[transformFX" local left_hand_back = "[combine:4x12:-52,-20="..skins.skins[name]..".png" local right_hand_back = "[combine:4x12:-52,-20="..skins.skins[name]..".png^[transformFX" formspec = formspec .. "image[3,1;2,2;".. head .. "] image[3,2.75;2,3;".. body .. "] image[3,5.35;1,3;".. left_leg .. "] image[3.82,5.35;1,3;".. right_leg .. "] image[2.2,2.75;1,3;".. left_hand .. "] image[4.63,2.75;1,3;".. right_hand .. "] image[7,1;2,2;".. back_head .. "] image[7,2.75;2,3;".. body_back .. "] image[7,5.35;1,3;".. left_leg_back .. "] image[7.82,5.35;1,3;".. right_leg_back .. "] image[6.2,2.75;1,3;".. left_hand_back .. "] image[8.63,2.75;1,3;".. right_hand_back .. "] image_button[0,1;2,2;"..head.."]" end end return formspec end -- Read the image size from a PNG file. (returns width, height) local PNG_HDR = string.char(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A) local function read_image_size(filename) local f = io.open(filename, "rb") if not f then return end f:seek("set", 0x0) local hdr = f:read(string.len(PNG_HDR)) if hdr ~= PNG_HDR then f:close() ; return end f:seek("set", 0x13) ; local ws = f:read(1) f:seek("set", 0x17) ; local hs = f:read(1) f:close() return ws:byte(), hs:byte() end -- update player skin skins.update_player_skin = function(player) if not player then return end local name = player:get_player_name() if minetest.get_modpath("player_api") then player_api.set_textures(player, {skins.skins[name] .. ".png"}) else default.player_set_textures(player, {skins.skins[name] .. ".png"}) end end skins.event_CHG = function(event, player) local name = player:get_player_name() local index = math.min(event.index, skins.id) if not skins.list[index] then return -- Do not update wrong skin number end skins.skins[name] = skins.list[index] skins.update_player_skin(player) if is_50 then local meta = player:get_meta() meta:set_string("skins:skin", skins.skins[name]) else player:set_attribute("skins:skin", skins.skins[name]) end end -- load player skin on join minetest.register_on_joinplayer(function(player) local name = player:get_player_name() ; if not name then return end local skin if is_50 then local meta = player:get_meta() skin = meta:get_string("skins:skin") else skin = player:get_attribute("skins:skins") end -- do we already have a skin in player attributes? if skin and skin ~= "" then skins.skins[name] = skin -- otherwise use skin from skins.mt file or default if not set elseif not skins.skins[name] then skins.skins[name] = "character_1" end skins.update_player_skin(player) end) -- admin command to set player skin (usually for custom skins) minetest.register_chatcommand("setskin", { params = " ", description = S("Admin command to set player skin"), privs = {server = true}, func = function(name, param) local playername, skin = string.match(param, "([^ ]+) (-?%d+)") if not playername or not skin then return false, S("** Insufficient or wrong parameters") end local player = minetest.get_player_by_name(playername) if not player then return false, S("** Player @1 not online!", playername) end -- this check is only used when custom skins aren't in use -- if not skins.list[tonumber(skin)] then -- return false, S("** Invalid skin number (max value is @1)", id) -- end skins.skins[playername] = "character_" .. tonumber(skin) skins.update_player_skin(player) if is_50 then local meta = player:get_meta() meta:set_string("skins:skin", skins.skins[playername]) else player:set_attribute("skins:skin", skins.skins[playername]) end minetest.chat_send_player(playername, S("Your skin has been set to") .. " character_" .. skin) return true, "** " .. playername .. S("'s skin set to") .. " character_" .. skin .. ".png" end, }) print ("[MOD] skins (Simple Skins) loaded")