add skinsdb5 - skinsdb based skins manager with preview support

This commit is contained in:
Alexander Weber 2020-05-25 08:13:20 +02:00
parent a7c395abc6
commit fd027e71d3
26 changed files with 608 additions and 0 deletions

9
mods/skinsdb5/API.md Normal file
View file

@ -0,0 +1,9 @@
# Skinsdb Interface
## skinsdb5.get_skinlist_for_player(playername)
Get all allowed skins for player. All public and all player's private skins. If playername not given only public skins returned
## skinsdb5.get_skinlist_with_meta(key, value)
Get all skins with metadata key is set to value. Example:
skinsdb5.get_skinlist_with_meta("playername", playername) - Get all private skins (w.o. public) for playername

24
mods/skinsdb5/README.md Normal file
View file

@ -0,0 +1,24 @@
# skinsdb5
This Minetest mod offers changeable player skins with a graphical interface for multiple inventory mods.
## Features
- player_api Skins management compatible, as proposed for minetest_game-0.5
- Skin change menu for sfinv (in minetest_game) and [unified_inventory](https://forum.minetest.net/viewtopic.php?t=12767)
- Own skin change menu and command line using chat command /skinsdb (set | show | list | list private | list public | ui)
- Skin previews supported in selection
- Additional information for each skin
- Support for different skins lists: public and a per-player list are currently implemented
## License
If nothing else is specified, it is licensed as GPLv3.
### Credits
- RealBadAngel (unified_inventory)
- Zeg9 (skinsdb)
- cornernote (source code)
- Krock (source code)
- bell07 (source code)

37
mods/skinsdb5/api.lua Normal file
View file

@ -0,0 +1,37 @@
local function skins_sort(skinslist)
table.sort(skinslist, function(a,b)
local a_sortid = a.sort_id or ""
local b_sortid = b.sort_id or ""
if a_sortid ~= b_sortid then
return a_sortid < b_sortid
else
return tostring(a.description or a.name or "") < tostring(b.description or b.name or "")
end
end)
end
-- Get skinlist for player. If no player given, public skins only selected
function skinsdb5.get_skinlist_for_player(playername)
local skinslist = {}
for _, skin in pairs(player_api.registered_skins) do
if skin.in_inventory_list ~= false and
(not skin.playername or (playername and skin.playername:lower() == playername:lower())) then
table.insert(skinslist, skin)
end
end
skins_sort(skinslist)
return skinslist
end
-- Get skinlist selected by metadata
function skinsdb5.get_skinlist_with_meta(key, value)
assert(key, "key parameter for skinsdb5.get_skinlist_with_meta() missed")
local skinslist = {}
for _, skin in pairs(player_api.registered_skins) do
if skin[key] == value then
table.insert(skinslist, skin)
end
end
skins_sort(skinslist)
return skinslist
end

View file

@ -0,0 +1,104 @@
local S = minetest.get_translator("skinsdb")
local function show_selection_formspec(player)
local context = skinsdb5.get_formspec_context(player)
local name = player:get_player_name()
local skin = player_api.get_skin(player)
local formspec = "size[8,8]"..skinsdb5.get_skin_info_formspec(skin)
formspec = formspec..skinsdb5.get_skin_selection_formspec(player, context, 3.5)
minetest.show_formspec(name, 'skinsdb_show_ui', formspec)
end
minetest.register_chatcommand("skinsdb", {
params = "[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]",
description = S("Show, list or set player's skin"),
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not player then
return false, S("Player not found")
end
-- parse command line
local command, parameter
local words = param:split(" ")
local word = words[1]
if word == 'set' or word == 'list' or word == 'show' or word == 'ui' then
command = word
parameter = words[2]
elseif player_api.registered_skins[word] then
command = 'set'
parameter = word
elseif not word then
command = 'ui'
else
return false, S("unknown command").." "..word..", "..S("see /help skinsdb for supported parameters")
end
if command == "set" then
if parameter then
if player_api.registered_skins[parameter] then
player_api.set_skin(player, parameter)
return true, S("skin set to").." "..parameter
else
return false, S("invalid skin").." "..parameter
end
else
return false, S("requires skin key")
end
elseif command == "list" then
local list
if parameter == "private" then
list = skinsdb5.get_skinlist_with_meta("playername", name)
elseif parameter == "public" then
list = skinsdb5.get_skinlist_for_player()
elseif not parameter then
list = skinsdb5.get_skinlist_for_player(name)
else
return false, S("unknown parameter"), parameter
end
local current_skin_key = player_api.get_skin(player)
for _, skin in ipairs(list) do
local info = skin.name.." - "
..S("Name").."="..(skin.description or skin.name or "").." "
..S("Author").."="..(skin.author or "").." "
..S("License").."="..(skin.license or "")
if skin.name == current_skin_key then
info = minetest.colorize("#00FFFF", info)
end
minetest.chat_send_player(name, info)
end
elseif command == "show" then
local skin
if parameter then
skin = parameter
else
skin = player_api.get_skin(player)
end
if not skin then
return false, S("invalid skin")
end
local formspec = "size[8,3]"..skinsdb5.get_skin_info_formspec(skin)
minetest.show_formspec(name, 'skinsdb_show_skin', formspec)
elseif command == "ui" then
show_selection_formspec(player)
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "skinsdb_show_ui" then
return
end
local context = skinsdb5.get_formspec_context(player)
local action = skinsdb5.on_skin_selection_receive_fields(player, context, fields)
if action == 'set' then
minetest.close_formspec(player:get_player_name(), formname)
elseif action == 'page' then
show_selection_formspec(player)
end
end)

132
mods/skinsdb5/formspecs.lua Normal file
View file

@ -0,0 +1,132 @@
local S = minetest.get_translator("skinsdb")
function skinsdb5.get_formspec_context(player)
if player then
local playername = player:get_player_name()
skinsdb5.ui_context[playername] = skinsdb5.ui_context[playername] or {}
return skinsdb5.ui_context[playername]
else
return {}
end
end
-- Show skin info
function skinsdb5.get_skin_info_formspec(skin_name)
local skin = player_api.registered_skins[skin_name]
if not skin then
return ""
end
local texture = skin.texture or (skin.textures and skin.textures[1])
if not skin.preview and texture then
skin.preview = skinsdb5.get_preview(texture, skin.format)
end
local formspec = ""
if skin.preview then
formspec = formspec.."image[0,.75;1,2;"..skin.preview.."]"
end
if texture then
local raw_size = skin.format == "1.8" and "2,2" or "2,1"
formspec = formspec.."label[6,.5;"..S("Raw texture")..":]image[6,1;"..raw_size..";"..texture.."]"
end
formspec = formspec.."label[2,.5;"..S("Name")..": "..minetest.formspec_escape(skin.description or skin.name).."]"
if skin.author then
formspec = formspec.."label[2,1;"..S("Author")..": "..minetest.formspec_escape(skin.author).."]"
end
if skin.license then
formspec = formspec.."label[2,1.5;"..S("License")..": "..minetest.formspec_escape(skin.license).."]"
end
return formspec
end
function skinsdb5.get_skin_selection_formspec(player, context, y_delta)
local skins_list = skinsdb5.get_skinlist_for_player(player:get_player_name())
local current_skin = player_api.registered_skins[player_api.get_skin(player)]
context.skins_list = {}
context.total_pages = 1
context.dropdown_values = nil
for i, skin in ipairs(skins_list) do
local page = math.floor((i-1) / 16)+1
local page_index = (i-1)%16+1
context.total_pages = page
context.skins_list[i] = {
name = skin.name,
page = page,
page_index = page_index
}
if not context.skins_page and skin.name == current_skin then
context.skins_page = page
end
end
context.skins_page = context.skins_page or 1
local current_page = context.skins_page
local formspec = ""
for i = (current_page-1)*16+1, current_page*16 do
local skin = skins_list[i]
if not skin then
break
end
if not skin.preview then
local texture = skin.texture or (skin.textures and skin.textures[1])
if texture then
skin.preview = skinsdb5.get_preview(texture, skin.format)
end
end
local index_p = context.skins_list[i].page_index
local x = (index_p-1) % 8
local y
if index_p > 8 then
y = y_delta + 1.9
else
y = y_delta
end
formspec = formspec.."image_button["..x..","..y..";1,2;"..
(skin.preview or "")..";skins_set$"..i..";]"..
"tooltip[skins_set$"..i..";"..minetest.formspec_escape(skin.description or skin.name).."]"
end
if context.total_pages > 1 then
local page_prev = current_page - 1
local page_next = current_page + 1
if page_prev < 1 then
page_prev = context.total_pages
end
if page_next > context.total_pages then
page_next = 1
end
local page_list = ""
context.dropdown_values = {}
for pg=1, context.total_pages do
local pagename = S("Page").." "..pg.."/"..context.total_pages
context.dropdown_values[pagename] = pg
if pg > 1 then page_list = page_list.."," end
page_list = page_list..pagename
end
formspec = formspec
.."button[0,"..(y_delta+4.0)..";1,.5;skins_page$"..page_prev..";<<]"
.."dropdown[0.9,"..(y_delta+3.88)..";6.5,.5;skins_selpg;"..page_list..";"..context.skins_page.."]"
.."button[7,"..(y_delta+4.0)..";1,.5;skins_page$"..page_next..";>>]"
end
return formspec
end
function skinsdb5.on_skin_selection_receive_fields(player, context, fields)
for field, _ in pairs(fields) do
local current = string.split(field, "$", 2)
if current[1] == "skins_set" then
player_api.set_skin(player, context.skins_list[tonumber(current[2])].name)
return 'set'
elseif current[1] == "skins_page" then
context.skins_page = tonumber(current[2])
return 'page'
end
end
if fields.skins_selpg then
context.skins_page = tonumber(context.dropdown_values[fields.skins_selpg])
return 'page'
end
end

29
mods/skinsdb5/init.lua Normal file
View file

@ -0,0 +1,29 @@
-- Rework 2018-2020 by bell07
-- License: GPLv3
skinsdb5 = {}
local modpath = minetest.get_modpath(minetest.get_current_modname())
skinsdb5.modpath = modpath
dofile(modpath.."/api.lua")
dofile(modpath.."/preview.lua")
dofile(modpath.."/formspecs.lua")
dofile(modpath.."/chatcommands.lua")
-- Unified inventory page/integration
if minetest.get_modpath("unified_inventory") then
dofile(modpath.."/unified_inventory_page.lua")
end
if minetest.get_modpath("sfinv") then
dofile(modpath.."/sfinv_page.lua")
end
-- Update skin on join
skinsdb5.ui_context = {}
minetest.register_on_leaveplayer(function(player)
skinsdb5.ui_context[player:get_player_name()] = nil
end)
-- Read current mod textures- and meta- folder
player_api.read_textures_and_meta()

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
# Translation by Xanthin
Raw texture=Rohtextur
Name=Name
Author=Autor
Change=Wechseln
Page=Seite
License=Lizenz
Description=Beschreibung
Show, list or set player's skin=Anzeigen oder setzen der Spieler-Skins
Player not found=Spieler nicht da
unknown command=unbekannter Befehl
see /help skinsdb for supported parameters=Lese /help für erlaubte Parameter
skin set to=Skin ist jetzt
invalid skin=unbekannter Skin
requires skin key=Skin key benötigt
unknown parameter=unbekannter Parameter
Skins=Aussehen

View file

@ -0,0 +1,18 @@
# textdomain: skinsdb
Raw texture=Texture
Name=Nom
Author=Auteur
Change=Changer
Page=Page
License=Licence
Description=Description
Show, list or set player's skin=Afficher, lister ou définir le skin du joueur
Player not found=Joueur non trouvé
unknown command=commande inconnue
see /help skinsdb for supported parameters=voir /help skinsdb pour les paramètres supportés
skin set to=skin définie sur
invalid skin=skin peau invalide
unknown parameter=paramètre inconnu
unknown skin=skin inconnue

View file

@ -0,0 +1,18 @@
# textdomain: skinsdb
# Malay translation by muhdnurhidayat
Raw texture=Tekstur mentah
Name=Nama
Author=Pencipta
Change=Ubah
Page=Halaman
License=Lesen
Description=Keterangan
Show, list or set player's skin=Tunjukkan, senaraikan atau tetapkan kulit pemain
Player not found=Pemain tidak dijumpai
unknown command=perintah tidak diketahui
see /help skinsdb for supported parameters=lihat /help skinsdb untuk parameter yang disokong
skin set to=kulit ditetapkan kepada
invalid skin=kulit tidak sah
unknown parameter=parameter tidak diketahui
unknown skin=kulit tidak diketahui

View file

@ -0,0 +1,20 @@
# textdomain: skinsdb
#Translation by IFRFSX(BingFengFSX)
Raw texture=自然状态的纹理
Name=名称
Author=作者
Change=更换
Page=页面
License=许可证
Description=说明
Show, list or set player's skin=显示,列出或者设置玩家的皮肤
Player not found=玩家未找到
unknown command=未知命令
see /help skinsdb for supported parameters=有关skinsdb支持的参数参见 /help
skin set to=皮肤设置为
invalid skin=无效皮肤
unknown parameter=未知参数
unknown skin=未知皮肤
Downloads the specified range of skins and shuts down the server=下载指定范围的皮肤并关闭服务器
Skins=皮肤

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
#Translation by IFRFSXBingFengFSX
Raw texture=自然狀態的紋理
Name=名稱
Author=作者
Change=更換
Page=頁面
License=許可證
Description=說明
Showlist or set player's skin=顯示,列出或者設定玩家的皮膚
Player not found=玩家未找到
unknown command=未知命令
see /help skinsdb for supported parameters=有關skinsdb支持的參數參見/help
skin set to=皮膚設定為
invalid skin=無效皮膚
unknown parameter=未知參數
unknown skin=未知皮膚
Downloads the specified range of skins and shuts down the server=下載指定範圍的皮膚並關閉服務器
Skins=皮膚

View file

@ -0,0 +1,20 @@
# textdomain: skinsdb
Raw texture=
Name=
Author=
Change=
Page=
License=
Description=
Show, list or set player's skin=
Player not found=
unknown command=
see /help skinsdb for supported parameters=
skin set to=
invalid skin=
requires skin key=
unknown parameter=
unknown skin=
Downloads the specified range of skins and shuts down the server=
Skins=

View file

@ -0,0 +1,3 @@
For each skin in textures folder a metadata file can be applied with ".txt" suffix.
character.png requires character.txt for example.
For file content see player_api/skin_api.txt for reference.

4
mods/skinsdb5/mod.conf Normal file
View file

@ -0,0 +1,4 @@
name = skinsdb5
description = Player skin mod, supporting sfinv and unified_inventory
depends = player_api
optional_depends = sfinv,unified_inventory

60
mods/skinsdb5/preview.lua Normal file
View file

@ -0,0 +1,60 @@
-- stohlen from https://github.com/GreenXenith/skinsdb/blob/master/skin_meta_api.lua
function skinsdb5.get_preview(texture, format)
local player_skin = "("..texture..")"
local skin = ""
-- Consistent on both sizes:
--Chest
skin = skin .. "([combine:16x32:-16,-12=" .. player_skin .. "^[mask:skindb_mask_chest.png)^"
--Head
skin = skin .. "([combine:16x32:-4,-8=" .. player_skin .. "^[mask:skindb_mask_head.png)^"
--Hat
skin = skin .. "([combine:16x32:-36,-8=" .. player_skin .. "^[mask:skindb_mask_head.png)^"
--Right Arm
skin = skin .. "([combine:16x32:-44,-12=" .. player_skin .. "^[mask:skindb_mask_rarm.png)^"
--Right Leg
skin = skin .. "([combine:16x32:0,0=" .. player_skin .. "^[mask:skindb_mask_rleg.png)^"
-- 64x skins have non-mirrored arms and legs
local left_arm
local left_leg
if format == "1.8" then
left_arm = "([combine:16x32:-24,-44=" .. player_skin .. "^[mask:(skindb_mask_rarm.png^[transformFX))^"
left_leg = "([combine:16x32:-12,-32=" .. player_skin .. "^[mask:(skindb_mask_rleg.png^[transformFX))^"
else
left_arm = "([combine:16x32:-44,-12=" .. player_skin .. "^[mask:skindb_mask_rarm.png^[transformFX)^"
left_leg = "([combine:16x32:0,0=" .. player_skin .. "^[mask:skindb_mask_rleg.png^[transformFX)^"
end
-- Left Arm
skin = skin .. left_arm
--Left Leg
skin = skin .. left_leg
-- Add overlays for 64x skins. these wont appear if skin is 32x because it will be cropped out
--Chest Overlay
skin = skin .. "([combine:16x32:-16,-28=" .. player_skin .. "^[mask:skindb_mask_chest.png)^"
--Right Arm Overlay
skin = skin .. "([combine:16x32:-44,-28=" .. player_skin .. "^[mask:skindb_mask_rarm.png)^"
--Right Leg Overlay
skin = skin .. "([combine:16x32:0,-16=" .. player_skin .. "^[mask:skindb_mask_rleg.png)^"
--Left Arm Overlay
skin = skin .. "([combine:16x32:-40,-44=" .. player_skin .. "^[mask:(skindb_mask_rarm.png^[transformFX))^"
--Left Leg Overlay
skin = skin .. "([combine:16x32:4,-32=" .. player_skin .. "^[mask:(skindb_mask_rleg.png^[transformFX))"
-- Full Preview
skin = "(((" .. skin .. ")^[resize:64x128)^[mask:skindb_transform.png)"
return minetest.formspec_escape(skin)
end
player_api.register_on_skin_change(function(player, model_name, skin_name)
local skin = player_api.registered_skins[skin_name]
if not skin.preview then
local texture = skin.texture or (skin.textures and skin.textures[1])
if texture then
skin.preview = skinsdb5.get_preview(texture, skin.format)
end
end
end)

View file

@ -0,0 +1,29 @@
local S = minetest.get_translator("skinsdb")
-- generate the current formspec
local function get_formspec(player, context)
local skin = player_api.get_skin(player)
local formspec = skinsdb5.get_skin_info_formspec(skin)
formspec = formspec..skinsdb5.get_skin_selection_formspec(player, context, 4)
return formspec
end
sfinv.register_page("skinsdb5:overview", {
title = S("Skins"),
get = function(self, player, context)
-- collect skins data
return sfinv.make_formspec(player, context, get_formspec(player, context))
end,
on_player_receive_fields = function(self, player, context, fields)
local action = skinsdb5.on_skin_selection_receive_fields(player, context, fields)
if action == "page" then
sfinv.set_player_inventory_formspec(player)
end
end
})
player_api.register_on_skin_change(function(player, model_name, skin_name)
if sfinv.enabled then
sfinv.set_player_inventory_formspec(player)
end
end)

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View file

@ -0,0 +1,6 @@
In this folder the skin files could be placed according the next file naming convention
character_[number].png - Public skin, available for all users
character_[name].png - Public skin, but named as "name" - usefull to enhance already provided skins like sam
player_[nick].png or player_[nick]_[number].png - one or multiple private skins for player "nick"
*_preview.png - Preview files for public and private skins - Optional, overrides the generated preview

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,57 @@
local S = minetest.get_translator("skinsdb")
unified_inventory.register_page("skins", {
get_formspec = function(player)
local skin = player_api.get_skin(player)
local formspec = "background[0.06,0.99;7.92,7.52;ui_misc_form.png]"..skinsdb5.get_skin_info_formspec(skin)..
"button[.75,3;6.5,.5;skins_page;"..S("Change").."]"
return {formspec=formspec}
end,
})
unified_inventory.register_button("skins", {
type = "image",
image = "skins_button.png",
tooltip = S("Skins"),
})
local function get_formspec(player)
local context = skinsdb5.get_formspec_context(player)
local formspec = "background[0.06,0.99;7.92,7.52;ui_misc_form.png]"..
skinsdb5.get_skin_selection_formspec(player, context, -0.2)
return formspec
end
unified_inventory.register_page("skins_page", {
get_formspec = function(player)
return {formspec=get_formspec(player)}
end
})
-- click button handlers
minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields.skins then
unified_inventory.set_inventory_formspec(player, "craft")
return
end
if formname ~= "" then
return
end
local context = skinsdb5.get_formspec_context(player)
local action = skinsdb5.on_skin_selection_receive_fields(player, context, fields)
if action == "set" then
unified_inventory.set_inventory_formspec(player, "skins")
elseif action == "page" then
unified_inventory.set_inventory_formspec(player, "skins_page")
end
end)
player_api.register_on_skin_change(function(player, model_name, skin_name)
local player_name = player:get_player_name()
-- refresh skins page if selected
if unified_inventory.current_page[player_name] == "skins" then
unified_inventory.set_inventory_formspec(player, "skins")
end
end)