write something there

This commit is contained in:
N-Nachtigal 2025-05-04 16:01:41 +02:00
commit b4b6c08f4f
8546 changed files with 309825 additions and 0 deletions

14
mods/skinsdb/.luacheckrc Normal file
View file

@ -0,0 +1,14 @@
unused_args = false
allow_defined_top = true
max_line_length = 999
globals = {
"minetest", "unified_inventory", "core",
"player_api", "clothing", "armor", "sfinv",
}
read_globals = {
string = {fields = {"split", "trim"}},
table = {fields = {"copy", "getn"}},
"hand_monoid",
}

128
mods/skinsdb/API.md Normal file
View file

@ -0,0 +1,128 @@
# Skinsdb Interface
## skins.get_player_skin(player)
Return the skin object assigned to the player. Returns default if nothing assigned
## skins.assign_player_skin(player, skin)
Check if allowed and assign the skin for the player without visual updates. The "skin" parameter could be the skin key or the skin object
Returns false if skin is not valid or applicable to player
## skins.update_player_skin(player)
Update selected skin visuals on player
## skins.set_player_skin(player, skin)
Function for external usage on skin selection. This function assign the skin, call the skin:set_skin(player) hook to update dynamic skins, then update the visuals
## skins.get_skin_format(file)
Returns the skin format version ("1.0" or "1.8"). File is an open file handle to the texture file
## skins.get_skinlist(assignment, select_unassigned)
Obsolete - use get_skinlist_for_player() or get_skinlist_with_meta() instead
## skins.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
## skins.get_skinlist_with_meta(key, value)
Get all skins with metadata key is set to value. Example:
skins.get_skinlist_with_meta("playername", playername) - Get all private skins (w.o. public) for playername
## skins.register_skin(path, filename)
Registers a new skin based on the texture file path specified by `path` and `filename`.
* `path` (string): points to the parent directory of the texture `filename`.
Generally, this should be in the format `mymod.modpath .. "/textures"`.
* `filename` (string): full file name, without any path specifications.
The file name must adhere to [one of the accepted naming formats](textures/readme.txt).
Note: this function takes the following files into consideration:
1. `<path>/<filename>` (required)
* Main skin texture
2. `<path>/<filenamestem><separator>preview.png` (optional)
* Pre-generated preview image
3. `<path>/../meta/<filenamestem>.txt` (optional)
* Metadata regarding the skin
Return values:
* On failure: `false, reason`
* `reason` (string): human readable reason string (similar to `io.open` errors)
* On success: `true, key`
* `key`: unique skins key for use with e.g. `skins.get(key)` for subsequent
fine-tuning of the skin registration.
## skins.new(key, object)
Create and register a new skin object for given key
- key: Unique skins key, like "character_1"
- object: Optional. Could be a prepared object with redefinitions
## skins.get(key)
Get existing skin object
HINT: During build-up phase maybe the next statement is usefull
```
local skin = skins.get(name) or skins.new(name)
```
# Skin object
## skin:get_key()
Get the unique skin key
## skin:set_texture(texture)
Set the skin texture - usually at the init time only
## skin:get_texture()
Get the skin texture for any reason. Note to apply them the skin:set_skin() should be used
Could be redefined for dynamic texture generation
## skin:set_hand(hand_node)
Set the hand node to be used with this skin
## skin:set_hand_from_texture()
Register and set hand node based on skin texture.
Uses different model depending on get_meta("format") ("1.0" or "1.8")
Only works on mod load
## skin:get_hand()
Get hand node. Returns ItemStack
## skin:set_preview(texture)
Set the skin preview - usually at the init time only
## skin:get_preview()
Get the skin preview
Could be redefined for dynamic preview texture generation
## skin:set_skin(player)
Hook for dynamic skins updates on select. Is called in skins.set_player_skin()
In skinsdb the default implementation for this function is empty.
## skin:apply_skin_to_player(player)
Apply the skin to the player. Called in skins.update_player_skin() to update visuals
## skin:set_meta(key, value)
Add a meta information to the skin object
Note: the information is not stored, therefore should be filled each time during skins registration
## skin:get_meta(key)
The next metadata keys are filled or/and used interally in skinsdb framework
- name - A name for the skin
- author - The skin author
- license - THe skin texture license
- assignment - (obsolete) is "player:playername" in case the skin is assigned to be private for a player
- playername - Player assignment for private skin. Set false for skins not usable by all players (like NPC-Skins), true or nothing for all player skins
- in_inventory_list - If set to false the skin is not visible in inventory skins selection but can be still applied to the player
- _sort_id - Thi skins lists are sorted by this field for output (internal key)
## skin:get_meta_string(key)
Same as get_meta() but does return "" instead of nil if the meta key does not exists
## skin:is_applicable_for_player(playername)
Returns whether this skin is applicable for player "playername" or not, like private skins

14
mods/skinsdb/CREDITS.md Normal file
View file

@ -0,0 +1,14 @@
The character textures in this mod are listed/credited below by license and author. Textures and metadata were downloaded from [the online SkinsDB service](https://skinsdb.terraqueststudios.net/). Changes were not made to these works; they are distributed in their original form.
- Characters released under [CC-BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
- "Farmer1" by sdzen (character_1.png)
- "Male" by TenPlus1 (character_2.png)
- "Female" by TenPlus1 (character_3.png)
- "Adventer girl" by lovehart (character_5.png)
- "Oliver_MV" by hansuke123 (character_6.png)
- "Flower Girl" by julito (character_7.png)
- "Trader 1" by TenPlus1 (character_8.png)
- "Angelo" by Extex (character_9.png)
- "santa" by jordan4ibanez (character_10.png)
- Characters released under [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
- "mbb_old" by mbb (character_4.png)

68
mods/skinsdb/README.md Normal file
View file

@ -0,0 +1,68 @@
# skinsdb
This Minetest mod offers changeable player skins with a graphical interface for multiple inventory mods.
## Features
- Flexible skins API to manage the database
- [character_creator](https://github.com/minetest-mods/character_creator) support for custom skins
- Skin change menu for sfinv (in minetest_game) and [unified_inventory](https://forum.minetest.net/viewtopic.php?t=12767)
- Skins change menu and command line using chat command /skinsdb (set | show | list | list private | list public | ui)
- Supported by [smart_inventory](https://forum.minetest.net/viewtopic.php?t=16597) for the skin selection
- Supported by [i3](https://github.com/minetest-mods/i3) inventory mod
- Skin previews supported in selection
- Additional information for each skin
- Support for different skins lists: public and a per-player list are currently implemented
- Full [3d_armor](https://forum.minetest.net/viewtopic.php?t=4654) support
- Compatible to 1.0 and 1.8 Minecraft skins format
- Skinned hand in 1st person view (1.0 skins only)
## Installing skins
### Download from the [database](https://skinsdb.terraqueststudios.net/)
#### Ingame Downloader
1) Get Minetest 5.1.0-dev-cb00632 or newer
2) In the settings menu show advanced options, find the "Developer Options" tab and add "skinsdb" to "Trusted mods" (secure.trusted_mods in minetest.conf)
3) Start your world
4) Run `/skinsdb_download_skins <skindb start page> <amount of pages>`
5) Wait for the Minetest server to shut down
6) Start the server again
You might want to run `minetest` in a Terminal/Console window to check the log output instantly.
#### Python Download script
**Requirements:**
* Python 3
* `requests` library: `pip3 install requests`
Go to the updater folder of this mod and run `python3 update_skins.py`
The Script will download all the skins from the database for you.
### Manual addition
1) Copy your skin textures to `textures` as documented in `textures/readme.txt`
2) Create `meta/character_<name>.txt` with the following fields (separated by new lines):
* Skin name
* Author
* Skin license
## License:
- GPLv3
- skin texture licenses: See "meta" folder
- hand model: CC0
### Credits
- RealBadAngel (unified_inventory)
- Zeg9 (skinsdb)
- cornernote (source code)
- Krock (source code)
- bell07 (source code)
- stujones11 (player models)
- jordan4ibanez (1st person view hand)

94
mods/skinsdb/api.lua Normal file
View file

@ -0,0 +1,94 @@
-- get current skin
local storage = minetest.get_mod_storage()
function skins.get_player_skin(player)
local player_name = player:get_player_name()
local meta = player:get_meta()
if meta:get("skinsdb:skin_key") then
-- Move player data prior July 2018 to mod storage
storage:set_string(player_name, meta:get_string("skinsdb:skin_key"))
meta:set_string("skinsdb:skin_key", "")
end
local skin_name = storage:get_string(player_name)
local skin = skins.get(skin_name)
if #skin_name > 0 and not skin then
-- Migration step to convert `_`-delimited skins to `.` (if possible)
skin = skins.__fuzzy_match_skin_name(player_name, skin_name, true)
if skin then
storage:set_string(player_name, skin:get_key())
else
storage:set_string(player_name, "")
end
end
return skin or skins.get(skins.default)
end
-- Assign skin to player
function skins.assign_player_skin(player, skin)
local skin_obj
if type(skin) == "string" then
skin_obj = skins.get(skin)
else
skin_obj = skin
end
if not skin_obj then
return false
end
if skin_obj:is_applicable_for_player(player:get_player_name()) then
local skin_key = skin_obj:get_key()
if skin_key == skins.default then
skin_key = ""
end
storage:set_string(player:get_player_name(), skin_key)
else
return false
end
return true, skin_obj
end
-- update visuals
function skins.update_player_skin(player)
if skins.armor_loaded then
-- all needed is wrapped and implemented in 3d_armor mod
armor:set_player_armor(player)
else
-- do updates manually without 3d_armor
skins.get_player_skin(player):apply_skin_to_player(player)
if minetest.global_exists("sfinv") and sfinv.enabled then
sfinv.set_player_inventory_formspec(player)
end
end
end
-- Assign and update - should be used on selection externally
function skins.set_player_skin(player, skin)
local success, skin_obj = skins.assign_player_skin(player, skin)
if success then
skins.get_player_skin(player):set_skin(player)
skins.update_player_skin(player)
minetest.log("action", player:get_player_name().." set skin to "..skin_obj:get_key(""))
end
return success
end
-- Check Skin format (code stohlen from stu's multiskin)
function skins.get_skin_format(file)
file:seek("set", 1)
if file:read(3) == "PNG" then
file:seek("set", 16)
local ws = file:read(4)
local hs = file:read(4)
local w = ws:sub(3, 3):byte() * 256 + ws:sub(4, 4):byte()
local h = hs:sub(3, 3):byte() * 256 + hs:sub(4, 4):byte()
if w >= 64 then
if w == h then
return "1.8"
elseif w == h * 2 then
return "1.0"
end
end
end
end

View file

@ -0,0 +1,103 @@
local S = minetest.get_translator("skinsdb")
local function show_selection_formspec(player)
local context = skins.get_formspec_context(player)
local name = player:get_player_name()
local skin = skins.get_player_skin(player)
local formspec = "size[8,8]"..skins.get_skin_info_formspec(skin)
formspec = formspec..skins.get_skin_selection_formspec(player, context, 3.5)
minetest.show_formspec(name, 'skinsdb_show_ui', formspec)
end
minetest.register_chatcommand("skinsdb", {
params = S("[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 skins.get(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
local success = skins.set_player_skin(player, parameter)
if success then
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 = skins.get_skinlist_with_meta("playername", name)
elseif parameter == "public" then
list = skins.get_skinlist_for_player()
elseif not parameter then
list = skins.get_skinlist_for_player(name)
else
return false, S("unknown parameter"), parameter
end
local current_skin_key = skins.get_player_skin(player):get_key()
for _, skin in ipairs(list) do
local info = skin:get_key().." - "
..S("Name").."="..skin:get_meta_string("name").." "
..S("Author").."="..skin:get_meta_string("author").." "
..S("License").."="..skin:get_meta_string("license")
if skin:get_key() == 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 = skins.get(parameter)
else
skin = skins.get_player_skin(player)
end
if not skin then
return false, S("invalid skin")
end
local formspec = "size[8,3]"..skins.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 = skins.get_formspec_context(player)
local action = skins.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)

172
mods/skinsdb/formspecs.lua Normal file
View file

@ -0,0 +1,172 @@
local S = minetest.get_translator("skinsdb")
local ui = minetest.global_exists("unified_inventory") and unified_inventory
function skins.get_formspec_context(player)
if player then
local playername = player:get_player_name()
skins.ui_context[playername] = skins.ui_context[playername] or {}
return skins.ui_context[playername]
else
return {}
end
end
-- Show skin info
function skins.get_skin_info_formspec(skin, perplayer_formspec)
local texture = skin:get_texture()
local m_name = skin:get_meta_string("name")
local m_author = skin:get_meta_string("author")
local m_license = skin:get_meta_string("license")
local m_format = skin:get_meta("format")
-- overview page
local raw_size = m_format == "1.8" and "2,2" or "2,1"
local lxoffs = 0.8
local cxoffs = 2
local rxoffs = 5.5
if type(perplayer_formspec) == "table" then -- we're using Unified Inventory
lxoffs = 1.5
cxoffs = 3.75
rxoffs = 7.5
end
local formspec = "image["..lxoffs..",.6;1,2;"..minetest.formspec_escape(skin:get_preview()).."]"
if texture then
formspec = formspec.."label["..rxoffs..",.5;"..S("Raw texture")..":]"
.."image["..rxoffs..",1;"..raw_size..";"..texture.."]"
end
if m_name ~= "" then
formspec = formspec.."label["..cxoffs..",.5;"..S("Name")..": "..minetest.formspec_escape(m_name).."]"
end
if m_author ~= "" then
formspec = formspec.."label["..cxoffs..",1;"..S("Author")..": "..minetest.formspec_escape(m_author).."]"
end
if m_license ~= "" then
formspec = formspec.."label["..cxoffs..",1.5;"..S("License")..": "..minetest.formspec_escape(m_license).."]"
end
return formspec
end
function skins.get_skin_selection_formspec(player, context, perplayer_formspec)
context.skins_list = skins.get_skinlist_for_player(player:get_player_name())
context.total_pages = 1
local xoffs = 0
local yoffs = 4
local xspc = 1
local yspc = 2
local skinwidth = 1
local skinheight = 2
local xscale = 1 -- luacheck: ignore
local btn_y = 8.15
local drop_y = 8
local btn_width = 1
local droppos = 1
local droplen = 6.25
local btn_right = 7
local maxdisp = 16
local ctrls_height = 0.5
if type(perplayer_formspec) == "table" then -- it's being used under Unified Inventory
xoffs = perplayer_formspec.std_inv_x
xspc = ui.imgscale
yspc = ui.imgscale*2
skinwidth = ui.imgscale*0.9
skinheight = ui.imgscale*1.9
xscale = ui.imgscale
btn_width = ui.imgscale
droppos = xoffs + btn_width + 0.1
droplen = ui.imgscale * 6 - 0.2
btn_right = droppos + droplen + 0.1
if perplayer_formspec.pagecols == 4 then -- and we're in lite mode
yoffs = 1
maxdisp = 8
drop_y = yoffs + skinheight + 0.1
else
yoffs = 0.2
drop_y = yoffs + skinheight*2 + 0.2
end
btn_y = drop_y
end
for i, skin in ipairs(context.skins_list ) do
local page = math.floor((i-1) / maxdisp)+1
skin:set_meta("inv_page", page)
skin:set_meta("inv_page_index", (i-1)%maxdisp+1)
context.total_pages = page
end
context.skins_page = context.skins_page or skins.get_player_skin(player):get_meta("inv_page") or 1
context.dropdown_values = nil
local page = context.skins_page
local formspec = ""
for i = (page-1)*maxdisp+1, page*maxdisp do
local skin = context.skins_list[i]
if not skin then
break
end
local index_p = skin:get_meta("inv_page_index")
local x = ((index_p-1) % 8) * xspc + xoffs
local y
if index_p > 8 then
y = yoffs + yspc
else
y = yoffs
end
formspec = formspec..
string.format("image_button[%f,%f;%f,%f;%s;skins_set$%i;]",
x, y, skinwidth, skinheight,
minetest.formspec_escape(skin:get_preview()), i)..
"tooltip[skins_set$"..i..";"..minetest.formspec_escape(skin:get_meta_string("name")).."]"
end
if context.total_pages > 1 then
local page_prev = page - 1
local page_next = 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..
string.format("button[%f,%f;%f,%f;skins_page$%i;<<]",
xoffs, btn_y, btn_width, ctrls_height, page_prev)..
string.format("button[%f,%f;%f,%f;skins_page$%i;>>]",
btn_right, btn_y, btn_width, ctrls_height, page_next)..
string.format("dropdown[%f,%f;%f,%f;skins_selpg;%s;%i]",
droppos, drop_y, droplen, ctrls_height, page_list, page)
end
return formspec
end
function skins.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
skins.set_player_skin(player, context.skins_list[tonumber(current[2])])
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

116
mods/skinsdb/init.lua Normal file
View file

@ -0,0 +1,116 @@
-- Unified Skins for Minetest - based modified Bags from unfied_inventory and skins from inventory_plus
-- Copyright (c) 2012 cornernote, Dean Montgomery
-- Rework 2017 by bell07
-- License: GPLv3
skins = {}
skins.modpath = minetest.get_modpath(minetest.get_current_modname())
skins.default = "character"
dofile(skins.modpath.."/skin_meta_api.lua")
dofile(skins.modpath.."/api.lua")
dofile(skins.modpath.."/skinlist.lua")
dofile(skins.modpath.."/formspecs.lua")
dofile(skins.modpath.."/chatcommands.lua")
-- Unified inventory page/integration
if minetest.get_modpath("unified_inventory") then
dofile(skins.modpath.."/unified_inventory_page.lua")
end
if minetest.get_modpath("sfinv") then
dofile(skins.modpath.."/sfinv_page.lua")
end
-- ie.loadfile does not exist?
skins.ie = minetest.request_insecure_environment()
skins.http = minetest.request_http_api()
dofile(skins.modpath.."/skins_updater.lua")
skins.ie = nil
skins.http = nil
-- 3d_armor compatibility
if minetest.global_exists("armor") then
skins.armor_loaded = true
armor.get_player_skin = function(self, name)
local skin = skins.get_player_skin(minetest.get_player_by_name(name))
return skin:get_texture()
end
armor.get_preview = function(self, name)
local skin = skins.get_player_skin(minetest.get_player_by_name(name))
return skin:get_preview()
end
armor.update_player_visuals = function(self, player)
if not player then
return
end
local skin = skins.get_player_skin(player)
skin:apply_skin_to_player(player)
armor:run_callbacks("on_update", player)
end
end
if minetest.global_exists("clothing") and clothing.player_textures then
skins.clothing_loaded = true
clothing:register_on_update(skins.update_player_skin)
end
-- Update skin on join
skins.ui_context = {}
minetest.register_on_joinplayer(function(player)
skins.update_player_skin(player)
end)
minetest.register_on_leaveplayer(function(player)
skins.ui_context[player:get_player_name()] = nil
player:get_inventory():set_size("hand", 0)
end)
minetest.register_on_shutdown(function()
for _, player in pairs(minetest.get_connected_players()) do
player:get_inventory():set_size("hand", 0)
end
end)
player_api.register_model("skinsdb_3d_armor_character_5.b3d", {
animation_speed = 30,
textures = {
"blank.png",
"blank.png",
"blank.png",
"blank.png"
},
animations = {
stand = {x=0, y=79},
lay = {x=162, y=166},
walk = {x=168, y=187},
mine = {x=189, y=198},
walk_mine = {x=200, y=219},
sit = {x=81, y=160},
-- compatibility w/ the emote mod
wave = {x = 192, y = 196, override_local = true},
point = {x = 196, y = 196, override_local = true},
freeze = {x = 205, y = 205, override_local = true},
},
})
-- Register default character.png if not part of this mod
local default_skin_obj = skins.get(skins.default)
if not default_skin_obj then
default_skin_obj = skins.new(skins.default)
default_skin_obj:set_texture("character.png")
default_skin_obj:set_meta("format", '1.0')
default_skin_obj:set_meta("_sort_id", 0)
default_skin_obj:set_meta("name", "Sam")
default_skin_obj:set_hand_from_texture()
end
-- Secure hand inventory slot
minetest.register_allow_player_inventory_action(function(player, action, inv, data)
if data.to_list == "hand" or data.from_list == "hand" or data.listname == "hand" then
return 0
end
end)
--dofile(skins.modpath.."/unittest.lua")

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
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=Benötigt Skin-Name
unknown parameter=unbekannter Parameter
Raw texture=Rohtextur
Page=Seite
Name=Name
Author=Autor
License=Lizenz
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=
Change=Wechseln
Skins=Aussehen

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=[set] <haŭtonomo> | show [<haŭtonomo>] | list | list private | list public | [ui]
Show, list or set player's skin=Montri haŭtojn («show»), listigi haŭtojn («list»), aŭ agordi onian haŭton («set»)
Player not found=Ludanto ne trovita
unknown command=nekonata ordono
see /help skinsdb for supported parameters=rulu «/help skinsdb» por vidi subtenatajn parametrojn
skin set to=haŭto agordita al
invalid skin=nevalida haŭto
Requires skin key=Postulas haŭtonomon
unknown parameter=nekonata parametro
Raw texture=Kruda bildo
Page=Paĝo
Name=Nomo
Author=Aŭtoro
License=Permesilo
<skindb start page> <amount of pages>=<komenca paĝo> <paĝokvanto>
Downloads the specified range of skins and shuts down the server=Elŝutinte haŭtojn el skindb inter la donitaj paĝoj, restartigas la servilon
Change=Ŝanĝi
Skins=Haŭtoj

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
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
Requires skin key=
unknown parameter=paramètre inconnu
Raw texture=Texture
Page=Page
Name=Nom
Author=Auteur
License=Licence
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=
Change=Changer
Skins=

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
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
Requires skin key=
unknown parameter=parameter tidak diketahui
Raw texture=Tekstur mentah
Page=Halaman
Name=Nama
Author=Pencipta
License=Lesen
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=
Change=Ubah
Skins=

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
Show, list or set player's skin=Mostrar, listar ou definir a skin do jogador
Player not found=Jogador não encontrado
unknown command=Comando desconhecido
see /help skinsdb for supported parameters= consulte /help skinsdb para obter os parâmetros suportados
skin set to=Aparência definida para
invalid skin=Aparência inválida
Requires skin key=Requer chave de aparência
unknown parameter=parâmetro desconhecido
Raw texture=Textura crua
Page=Página
Name=Nome
Author=Autor
License=Licença
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=Baixa o intervalo especificado de capas e desliga o servidor
Change=Mudar
Skins=Aparência

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
Show, list or set player's skin=Показать скин, список скинов, установить скин игрока
Player not found=Игрок не найден
unknown command=неизвестная команда
see /help skinsdb for supported parameters=смотрите /help skinsdb для просмотра параметров
skin set to=установлено скин
invalid skin=некорректный скин
Requires skin key=Зависимый идентификатор скина
unknown parameter=неопределенный параметр
Raw texture=Текстура
Page=Страница
Name=Имя
Author=Автор
License=Лицензия
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=Загрузить массив скинов и остановить сервер
Change=Изменить
Skins=Скины

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
Show, list or set player's skin=Показати скін, список скінів, встановити скін гравця
Player not found=Гравець не знайдений
unknown command=невідома команда
see /help skinsdb for supported parameters=дивіться /help skinsdb для перегляду параметрів
skin set to=встановлено скін
invalid skin=некоректний скін
Requires skin key=Залежний ідентифікатор скіна
unknown parameter=невизначений параметр
Raw texture=Текстура
Page=Сторінка
Name=Ім'я
Author=Автор
License=Ліцензія
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=Завантажити масив скінів та зупинити сервер
Change=Змінити
Skins=Скіни

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
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=无效皮肤
Requires skin key=
unknown parameter=未知参数
Raw texture=自然状态的纹理
Page=页面
Name=名称
Author=作者
License=许可证
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=下载指定范围的皮肤并关闭服务器
Change=更换
Skins=皮肤

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
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=無效皮膚
Requires skin key=
unknown parameter=未知參數
Raw texture=自然狀態的紋理
Page=頁面
Name=名稱
Author=作者
License=許可證
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=下載指定範圍的皮膚並關閉服務器
Change=更換
Skins=皮膚

View file

@ -0,0 +1,19 @@
# textdomain: skinsdb
[set] <skin key> | show [<skin key>] | list | list private | list public | [ui]=
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=
Raw texture=
Page=
Name=
Author=
License=
<skindb start page> <amount of pages>=
Downloads the specified range of skins and shuts down the server=
Change=
Skins=

View file

@ -0,0 +1,3 @@
Farmer1
sdzen
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
santa
jordan4ibanez
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
Male
TenPlus1
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
Female
TenPlus1
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
mbb_old
mbb
CC 0 (1.0)

View file

@ -0,0 +1,3 @@
Adventer girl
lovehart
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
Oliver_MV
hansuke123
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
Flower Girl
julito
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
Trader 1
TenPlus1
CC BY-SA 3.0

View file

@ -0,0 +1,3 @@
Angelo
Extex
CC BY-SA 3.0

View file

@ -0,0 +1,5 @@
For each skin in textures folder a metadata file can be applied with "txt" suffilx. See character.txt for skin character.png for reference.
The file contains:
Skin name
Author
License

5
mods/skinsdb/mod.conf Normal file
View file

@ -0,0 +1,5 @@
name = skinsdb
description = Player skin mod, supporting unified_inventory, sfinv and smart_inventory
depends = player_api
optional_depends = unified_inventory,3d_armor,clothing,sfinv,hand_monoid
min_minetest_version = 5.4.0

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,21 @@
local S = minetest.get_translator("skinsdb")
-- generate the current formspec
local function get_formspec(player, context)
local skin = skins.get_player_skin(player)
local formspec = skins.get_skin_info_formspec(skin)
formspec = formspec..skins.get_skin_selection_formspec(player, context, 4)
return formspec
end
sfinv.register_page("skins: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)
skins.on_skin_selection_receive_fields(player, context, fields)
sfinv.set_player_inventory_formspec(player)
end
})

View file

@ -0,0 +1,240 @@
skins.meta = {}
local has_hand_monoid = minetest.get_modpath("hand_monoid")
local skin_class = {}
skin_class.__index = skin_class
skins.skin_class = skin_class
-----------------------
-- Class methods
-----------------------
-- constructor
function skins.new(key, object)
assert(key, 'Unique skins key required, like "character_1"')
local self = object or {}
setmetatable(self, skin_class)
self.__index = skin_class
self._key = key
self._sort_id = 0
skins.meta[key] = self
return self
end
-- getter
function skins.get(key)
return skins.meta[key]
end
-- Skin methods
-- In this implementation it is just access to attrubutes wrapped
-- but this way allow to redefine the functionality for more complex skins provider
function skin_class:get_key()
return self._key
end
function skin_class:set_meta(key, value)
self[key] = value
end
function skin_class:get_meta(key)
return self[key]
end
function skin_class:get_meta_string(key)
return tostring(self:get_meta(key) or "")
end
function skin_class:set_texture(value)
self._texture = value
end
function skin_class:get_texture()
return self._texture
end
function skin_class:set_hand(hand)
self._hand = hand
end
local ALPHA_CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or true
function skin_class:set_hand_from_texture()
local hand = core.get_current_modname()..':'..self._texture:gsub('[%p%c%s]', '')
local hand_def = {}
for k,v in pairs(minetest.registered_items[""]) do
if k ~= "mod_origin" and k ~= "type" and k ~= "wield_image" then
hand_def[k] = v
end
end
hand_def.tiles = {self:get_texture()}
hand_def.visual_scale = 1
hand_def.wield_scale = {x=1,y=1,z=1}
hand_def.paramtype = "light"
hand_def.drawtype = "mesh"
if(self:get_meta("format") == "1.0") then
hand_def.mesh = "skinsdb_hand.b3d"
else
hand_def.mesh = "skinsdb_hand_18.b3d"
end
hand_def.use_texture_alpha = ALPHA_CLIP
minetest.register_node(hand, hand_def)
self:set_hand(hand)
end
function skin_class:get_hand()
return self._hand
end
function skin_class:set_preview(value)
self._preview = value
end
function skin_class:get_preview()
if self._preview then
return self._preview
end
local player_skin = "("..self:get_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 self:get_meta("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 skin
end
function skin_class:apply_skin_to_player(player)
local function concat_texture(base, ext)
if base == "blank.png" then
return ext
elseif ext == "blank.png" then
return base
else
return base .. "^" .. ext
end
end
local playername = player:get_player_name()
local ver = self:get_meta("format") or "1.0"
player_api.set_model(player, "skinsdb_3d_armor_character_5.b3d")
local v10_texture = "blank.png"
local v18_texture = "blank.png"
local armor_texture = "blank.png"
local wielditem_texture = "blank.png"
if ver == "1.8" then
v18_texture = self:get_texture()
else
v10_texture = self:get_texture()
end
-- Support for clothing
if skins.clothing_loaded and clothing.player_textures[playername] then
local cape = clothing.player_textures[playername].cape
local layers = {}
for k, v in pairs(clothing.player_textures[playername]) do
if k ~= "skin" and k ~= "cape" then
table.insert(layers, v)
end
end
local overlay = table.concat(layers, "^")
v10_texture = concat_texture(v10_texture, cape)
v18_texture = concat_texture(v18_texture, overlay)
end
-- Support for armor
if skins.armor_loaded then
local armor_textures = armor.textures[playername]
if armor_textures then
armor_texture = concat_texture(armor_texture, armor_textures.armor)
wielditem_texture = concat_texture(wielditem_texture, armor_textures.wielditem)
end
end
player_api.set_textures(player, {
v10_texture,
v18_texture,
armor_texture,
wielditem_texture,
})
player:set_properties({
visual_size = {
x = self:get_meta("visual_size_x") or 1,
y = self:get_meta("visual_size_y") or 1
}
})
local hand = self:get_hand()
if has_hand_monoid then
if hand then
hand_monoid.monoid:add_change(player, {name = hand}, "skinsdb:hand")
else
hand_monoid.monoid:del_change(player, "skinsdb:hand")
end
else
if hand then
player:get_inventory():set_size("hand", 1)
player:get_inventory():set_stack("hand", 1, hand)
else
player:get_inventory():set_stack("hand", 1, "")
end
end
end
function skin_class:set_skin(player)
-- The set_skin is used on skins selection
-- This means the method could be redefined to start an furmslec
-- See character_creator for example
end
function skin_class:is_applicable_for_player(playername)
local assigned_player = self:get_meta("playername")
return assigned_player == nil or assigned_player == true or
playername and (minetest.check_player_privs(playername, {server=true}) or
assigned_player:lower() == playername:lower())
end

201
mods/skinsdb/skinlist.lua Normal file
View file

@ -0,0 +1,201 @@
local dbgprint = false and print or function() end
--- @param path Path to the "textures" directory, without tailing slash.
--- @param filename Current file name, such as "player.groot.17.png".
--- @return On error: false, error message. On success: true, skin key
function skins.register_skin(path, filename)
-- See "textures/readme.txt" for allowed formats
local prefix, sep, identifier, extension = filename:match("^(%a+)([_.])([%w_.-]+)%.(%a+)$")
--[[
prefix: "character" or "player"
sep: "." (new) or "_" (legacy)
identifier: number, name or (name + sep + number)
^ previews are explicity skipped
extension: "png" only due `skins.get_skin_format`
]]
-- Filter out files that do not match the allowed patterns
if not extension or extension:lower() ~= "png" then
return false, "invalid skin name"
end
if prefix ~= "player" and prefix ~= "character" then
return false, "unknown type"
end
local preview_suffix = sep .. "preview"
if identifier:sub(-#preview_suffix) == preview_suffix then
-- The preview texture is added by the main skin texture (if exists)
return false, "preview texture"
end
assert(path)
if path == ":UNITTEST:" then
path = nil
end
dbgprint("Found skin", prefix, identifier, extension)
local sort_id -- number, sorting "rank" in the skin list
local playername -- string, if player-specific
if prefix == "player" then
-- Allow "player.PLAYERNAME.png" and "player.PLAYERNAME.123.png"
local splits = identifier:split(sep)
playername = splits[1]
-- Put in front
sort_id = 0 + (tonumber(splits[2]) or 0)
if #splits > 1 and sep == "_" then
minetest.log("warning", "skinsdb: The skin name '" .. filename .. "' is ambigous." ..
" Please use the separator '.' to lock it down to the correct player name.")
end
else -- Public skin "character*"
-- Less priority
sort_id = 5000 + (tonumber(identifier) or 0)
end
local filename_noext = prefix .. sep .. identifier
dbgprint("Register skin", filename_noext, playername, sort_id)
-- Register skin texture
local skin_obj = skins.get(filename_noext) or skins.new(filename_noext)
skin_obj:set_texture(filename)
skin_obj:set_meta("_sort_id", sort_id)
if sep ~= "_" then
skin_obj._legacy_name = filename_noext:gsub("[._]+", "_")
end
if playername then
skin_obj:set_meta("assignment", "player:"..playername)
skin_obj:set_meta("playername", playername)
end
if path then
-- Get type of skin based on dimensions
local file = io.open(path .. "/" .. filename, "r")
local skin_format = skins.get_skin_format(file)
skin_obj:set_meta("format", skin_format)
file:close()
end
skin_obj:set_hand_from_texture()
skin_obj:set_meta("name", identifier)
if path then
-- Optional skin information
local file = io.open(path .. "/../meta/" .. filename_noext .. ".txt", "r")
if file then
dbgprint("Found meta")
local data = string.split(file:read("*all"), "\n", 3)
skin_obj:set_meta("name", data[1])
skin_obj:set_meta("author", data[2])
skin_obj:set_meta("license", data[3])
file:close() -- do not rely on delayed GC
end
end
if path then
-- Optional preview texture
local preview_name = filename_noext .. sep .. "preview.png"
local fh = io.open(path .. "/" .. preview_name)
if fh then
dbgprint("Found preview", preview_name)
skin_obj:set_preview(preview_name)
fh:close() -- do not rely on delayed GC
end
end
return true, skin_obj:get_key()
end
--- Internal function. Fallback/migration code for `.`-delimited skin names that
--- were equipped between d3c7fa7 and 312780c (master branch).
--- During this period, `.`-delimited skin names were internally registered with
--- `_` delimiters. This function tries to find a matching skin.
--- @param player_name (string)
--- @param skin_name (string) e.g. `player_foo_mc_bar`
--- @param be_noisy (boolean) whether to print a warning in case of mismatches`
--- @return On match, the new skin (skins.skin_class) or `nil` if nothing matched.
function skins.__fuzzy_match_skin_name(player_name, skin_name, be_noisy)
if select(2, skin_name:gsub("%.", "")) > 0 then
-- Not affected by ambiguity
return
end
for _, skin in pairs(skins.meta) do
if skin._legacy_name == skin_name then
dbgprint("Match", skin_name, skin:get_key())
return skin
end
--dbgprint("Try match", skin_name, skin:get_key(), skin._legacy_name)
end
if be_noisy then
minetest.log("warning", "skinsdb: cannot find matching skin '" ..
skin_name .. "' for player '" .. player_name .. "'.")
end
end
do
-- Load skins from the current mod directory
local skins_path = skins.modpath.."/textures"
local skins_dir_list = minetest.get_dir_list(skins_path)
for _, fn in pairs(skins_dir_list) do
skins.register_skin(skins_path, fn)
end
end
local function skins_sort(skinslist)
table.sort(skinslist, function(a,b)
local a_id = a:get_meta("_sort_id") or 10000
local b_id = b:get_meta("_sort_id") or 10000
if a_id ~= b_id then
return a_id < b_id
else
return (a:get_meta("name") or 'ZZ') < (b:get_meta("name") or 'ZZ')
end
end)
end
-- (obsolete) get skinlist. If assignment given ("mod:wardrobe" or "player:bell07") select skins matches the assignment. select_unassigned selects the skins without any assignment too
function skins.get_skinlist(assignment, select_unassigned)
minetest.log("deprecated", "skins.get_skinlist() is deprecated. Use skins.get_skinlist_for_player() instead")
local skinslist = {}
for _, skin in pairs(skins.meta) do
if not assignment or
assignment == skin:get_meta("assignment") or
(select_unassigned and skin:get_meta("assignment") == nil) then
table.insert(skinslist, skin)
end
end
skins_sort(skinslist)
return skinslist
end
-- Get skinlist for player. If no player given, public skins only selected
function skins.get_skinlist_for_player(playername)
local skinslist = {}
for _, skin in pairs(skins.meta) do
if skin:is_applicable_for_player(playername) and skin:get_meta("in_inventory_list") ~= false then
table.insert(skinslist, skin)
end
end
skins_sort(skinslist)
return skinslist
end
-- Get skinlist selected by metadata
function skins.get_skinlist_with_meta(key, value)
assert(key, "key parameter for skins.get_skinlist_with_meta() missed")
local skinslist = {}
for _, skin in pairs(skins.meta) do
if skin:get_meta(key) == value then
table.insert(skinslist, skin)
end
end
skins_sort(skinslist)
return skinslist
end

View file

@ -0,0 +1,151 @@
-- Skins update script
local S = minetest.get_translator("skinsdb")
local _ID_ = "Lua Skins Updater"
local internal = {}
internal.errors = {}
-- Binary downloads are required
if not core.features.httpfetch_binary_data then
internal.errors[#internal.errors + 1] =
"Feature 'httpfetch_binary_data' is missing. Update Minetest."
end
-- Insecure environment for saving textures and meta
local ie, http = skins.ie, skins.http
if not ie or not http then
internal.errors[#internal.errors + 1] = "Insecure environment is required. " ..
"Please add skinsdb to `secure.trusted_mods` in minetest.conf"
end
minetest.register_chatcommand("skinsdb_download_skins", {
params = S("<skindb start page> <amount of pages>"),
description = S("Downloads the specified range of skins and shuts down the server"),
privs = {server=true},
func = function(name, param)
if #internal.errors > 0 then
return false, "Cannot run " .. _ID_ .. ":\n\t" ..
table.concat(internal.errors, "\n\t")
end
local parts = string.split(param, " ")
local start = tonumber(parts[1])
local len = tonumber(parts[2])
if not (start and len and len > 0) then
return false, "Invalid page number or amount of pages"
end
internal.get_pages_count(internal.fetch_function, start, len)
return true, "Started downloading..."
end,
})
if #internal.errors > 0 then
return -- Nonsense to load something that's not working
end
-- http://minetest.fensta.bplaced.net/api/apidoku.md
local root_url = "http://skinsdb.terraqueststudios.net"
local page_url = root_url .. "/api/v1/content?client=mod&page=%i" -- [1] = Page#
local download_path = skins.modpath
local meta_path = download_path .. "/meta/"
local skins_path = download_path .. "/textures/"
-- Fancy debug wrapper to download an URL
local function fetch_url(url, callback)
http.fetch({
url = url,
user_agent = _ID_
}, function(result)
if result.succeeded then
if result.code ~= 200 then
core.log("warning", ("%s: STATUS=%i URL=%s"):format(
_ID_, result.code, url))
end
return callback(result.data)
end
core.log("warning", ("%s: Failed to download URL=%s"):format(
_ID_, url))
end)
end
-- Insecure workaround since meta/ and textures/ cannot be written to
local function unsafe_file_write(path, contents)
local f = ie.io.open(path, "wb")
f:write(contents)
f:close()
end
-- Takes a valid skin table from the Skins Database and saves it
local function save_single_skin(skin)
local meta = {
skin.name,
skin.author,
skin.license
}
local name = "character." .. skin.id
do
local legacy_name = "character_" .. skin.id
local fh = ie.io.open(skins_path .. legacy_name .. ".png", "r")
-- Use the old name if either the texture ...
if fh then
name = legacy_name
end
end
-- core.safe_file_write does not work here
unsafe_file_write(
meta_path .. name .. ".txt",
table.concat(meta, "\n")
)
unsafe_file_write(
skins_path .. name .. ".png",
core.decode_base64(skin.img)
)
core.log("action", ("%s: Completed skin %s"):format(_ID_, name))
end
-- Get total pages since it'll just return the last page all over again
internal.get_pages_count = function(callback, ...)
local vars = {...}
fetch_url(page_url:format(1) .. "&per_page=1", function(data)
local list = core.parse_json(data)
-- "per_page" defaults to 20 if left away (docs say something else, though)
callback(math.ceil(list.pages / 20), unpack(vars))
end)
end
-- Function to fetch a range of pages
internal.fetch_function = function(pages_total, start_page, len)
start_page = math.max(start_page, 1)
local end_page = math.min(start_page + len - 1, pages_total)
for page_n = start_page, end_page do
local page_cpy = page_n
fetch_url(page_url:format(page_n), function(data)
core.log("action", ("%s: Page %i"):format(_ID_, page_cpy))
local list = core.parse_json(data)
for i, skin in pairs(list.skins) do
assert(skin.type == "image/png")
assert(skin.id ~= "")
if skin.id ~= 1 then -- Skin 1 is bundled with skinsdb
save_single_skin(skin)
end
end
if page_cpy == end_page then
local log = _ID_ .. " finished downloading all skins. " ..
"Shutting down server to reload media cache"
core.log("action", log)
core.request_shutdown(log, true, 3 --[[give some time for pending requests]])
end
end)
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,33 @@
This location is where you can put your custom skins.
List of accepted texture names
------------------------------
Public skin available for all users:
character.[number or name].png
^ The allowed characters in "[number or name]" are "[A-z0-9_.-]+".
One or multiple private skins for player "[nick]":
player.[nick].png
player.[nick].[number or name].png
Skin previews for public and private skins:
character.[number or name].preview.png
player.[nick].preview.png
player.[nick].[number or name].preview.png
Note: This is optional and overrides automatically generated preciewws.
Legacy texture names
--------------------
The character `_` is accepted in player names, thus it is not recommended to
use such file names. For compatibility reasons, they are still recognized.
character_[number or name].png
player_[nick].png
player_[nick]_[number or name].png
... and corresponding previews that end in `_preview.png`.

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

View file

@ -0,0 +1,52 @@
local S = minetest.get_translator("skinsdb")
unified_inventory.register_page("skins", {
get_formspec = function(player, perplayer_formspec)
local skin = skins.get_player_skin(player)
local boffs = (type(perplayer_formspec) == "table") and 2 or 0.75
local formspec = perplayer_formspec.standard_inv_bg..
skins.get_skin_info_formspec(skin, perplayer_formspec)..
"button["..boffs..",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, perplayer_formspec)
local context = skins.get_formspec_context(player)
local formspec = perplayer_formspec.standard_inv_bg..
skins.get_skin_selection_formspec(player, context, perplayer_formspec)
return formspec
end
unified_inventory.register_page("skins_page", {
get_formspec = function(player, perplayer_formspec)
return {formspec=get_formspec(player, perplayer_formspec)}
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 = skins.get_formspec_context(player)
local action = skins.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)

51
mods/skinsdb/unittest.lua Normal file
View file

@ -0,0 +1,51 @@
local function get_skin(skin_name)
local skin = skins.get(skin_name)
or skins.__fuzzy_match_skin_name("(unittest)", skin_name, true)
return skin and skin:get_key() or nil
end
local function run_unittest()
local PATH = ":UNITTEST:"
-- -----
-- `.`: Simple register + retrieve operations
assert(skins.register_skin(PATH, "player.DotSep.png"))
assert(skins.register_skin(PATH, "player._DotSep_666_.1.png"))
assert(skins.register_skin(PATH, "character._DotSep_With-Dash-.png"))
assert(get_skin("player.DotSep"))
assert(get_skin("player._DotSep_666_.1"))
assert(get_skin("player.DotSep.1") == nil)
assert(get_skin("character._DotSep_With-Dash-"))
-- -----
-- Ambiguous skin names (filenames without extension). Register + retrieve
skins.new("player_AmbSki")
skins.new("player_AmbSki_1")
skins.new("player_AmbSki_666_1")
assert(get_skin("player_AmbSki"))
assert(get_skin("player_AmbSki_") == nil)
assert(get_skin("player_AmbSki_1"))
assert(get_skin("player_AmbSki_666_1"))
-- There are no `__` patterns as they were silently removed by string.split
-- -----
-- Mod Storage backwards compatibility
-- Match the old `_` notation to `.`-separated skins
skins.register_skin(PATH, "player.ComPat42.png")
skins.register_skin(PATH, "player.ComPat42.5.png")
skins.register_skin(PATH, "player._Com_Pat_42.png")
skins.register_skin(PATH, "player._Com_Pat_42.1.png")
assert(get_skin("player_ComPat42") == "player.ComPat42")
assert(get_skin("player_ComPat42_5") == "player.ComPat42.5")
assert(get_skin("player_Com_Pat_42") == "player._Com_Pat_42")
assert(get_skin("player_Com_Pat_42_1") == "player._Com_Pat_42.1")
error("Unittest passed! Please disable them now.")
end
run_unittest()