2024-06-05 17:55:55 +02:00
local dbgprint = false and print or function ( ) end
2017-06-18 23:15:58 +02:00
2024-06-05 17:55:55 +02:00
--- @param path Path to the "textures" directory, without tailing slash.
--- @param filename Current file name, such as "player.groot.17.png".
2024-06-15 15:16:52 +02:00
--- @return On error: false, error message. On success: true, skin key
function skins . register_skin ( path , filename )
2024-06-05 17:55:55 +02:00
-- See "textures/readme.txt" for allowed formats
2017-06-18 17:56:24 +02:00
2024-09-28 11:10:44 +02:00
local prefix , sep , identifier , extension = filename : match ( " ^(%a+)([_.])([%w_.-]+)%.(%a+)$ " )
2024-06-05 17:55:55 +02:00
--[[
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 `
] ]
2018-07-16 20:42:54 +02:00
2024-06-05 17:55:55 +02:00
-- Filter out files that do not match the allowed patterns
if not extension or extension : lower ( ) ~= " png " then
2024-06-15 15:16:52 +02:00
return false , " invalid skin name "
2024-06-05 17:55:55 +02:00
end
if prefix ~= " player " and prefix ~= " character " then
2024-06-15 15:16:52 +02:00
return false , " unknown type "
2024-06-05 17:55:55 +02:00
end
local preview_suffix = sep .. " preview "
if identifier : sub ( -# preview_suffix ) == preview_suffix then
2024-06-15 15:16:52 +02:00
-- 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
2024-06-05 17:55:55 +02:00
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 )
2018-07-16 20:42:54 +02:00
2024-06-05 17:55:55 +02:00
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. " )
2017-07-24 17:39:16 +02:00
end
2024-06-05 17:55:55 +02:00
else -- Public skin "character*"
-- Less priority
sort_id = 5000 + ( tonumber ( identifier ) or 0 )
end
local filename_noext = prefix .. sep .. identifier
2018-07-16 20:42:54 +02:00
2024-06-05 17:55:55 +02:00
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 )
2024-06-15 15:16:52 +02:00
if sep ~= " _ " then
skin_obj._legacy_name = filename_noext : gsub ( " [._]+ " , " _ " )
end
2024-06-05 17:55:55 +02:00
if playername then
skin_obj : set_meta ( " assignment " , " player: " .. playername )
skin_obj : set_meta ( " playername " , playername )
end
2024-06-15 15:16:52 +02:00
if path then
2024-06-05 17:55:55 +02:00
-- 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 )
2024-06-15 15:16:52 +02:00
if path then
2024-06-05 17:55:55 +02:00
-- 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 ] )
2024-07-22 18:41:11 +02:00
file : close ( ) -- do not rely on delayed GC
2017-06-18 17:56:24 +02:00
end
2024-06-05 17:55:55 +02:00
end
2017-06-18 17:56:24 +02:00
2024-06-15 15:16:52 +02:00
if path then
2024-06-05 17:55:55 +02:00
-- 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 )
2024-07-22 18:41:11 +02:00
fh : close ( ) -- do not rely on delayed GC
2017-06-15 13:45:42 +02:00
end
2014-07-23 13:46:42 +02:00
end
2024-06-15 15:16:52 +02:00
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
2017-02-09 23:06:25 +01:00
end
2017-06-15 13:45:42 +02:00
2024-06-05 17:55:55 +02:00
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
2024-06-15 15:16:52 +02:00
skins.register_skin ( skins_path , fn )
2024-06-05 17:55:55 +02:00
end
end
2019-08-09 06:54:47 +02:00
local function skins_sort ( skinslist )
table.sort ( skinslist , function ( a , b )
2019-10-29 08:11:38 +01:00
local a_id = a : get_meta ( " _sort_id " ) or 10000
local b_id = b : get_meta ( " _sort_id " ) or 10000
2019-08-09 06:54:47 +02:00
if a_id ~= b_id then
2022-02-16 10:18:30 +01:00
return a_id < b_id
2019-08-09 06:54:47 +02:00
else
2022-02-16 10:18:30 +01:00
return ( a : get_meta ( " name " ) or ' ZZ ' ) < ( b : get_meta ( " name " ) or ' ZZ ' )
2019-08-09 06:54:47 +02:00
end
end )
end
2018-01-07 14:50:22 +01:00
-- (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
2017-06-18 17:56:24 +02:00
function skins . get_skinlist ( assignment , select_unassigned )
2018-01-07 14:50:22 +01:00
minetest.log ( " deprecated " , " skins.get_skinlist() is deprecated. Use skins.get_skinlist_for_player() instead " )
2017-06-18 23:15:58 +02:00
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 )
2017-06-17 00:23:39 +02:00
end
end
2019-08-09 06:54:47 +02:00
skins_sort ( skinslist )
2017-06-18 23:15:58 +02:00
return skinslist
2017-06-15 16:16:57 +02:00
end
2018-01-07 14:50:22 +01:00
-- 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
2018-01-07 20:30:27 +01:00
if skin : is_applicable_for_player ( playername ) and skin : get_meta ( " in_inventory_list " ) ~= false then
2018-01-07 14:50:22 +01:00
table.insert ( skinslist , skin )
end
end
2019-08-09 06:54:47 +02:00
skins_sort ( skinslist )
2018-01-07 14:50:22 +01:00
return skinslist
end
2018-01-07 19:09:52 +01:00
-- 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
2019-08-09 06:54:47 +02:00
skins_sort ( skinslist )
2018-01-07 19:09:52 +01:00
return skinslist
end