Add simple skins skins support

This commit is contained in:
Johannes Fritz 2022-09-06 12:51:43 -05:00
parent 8e6b6393c2
commit 413c6292ff
11 changed files with 292 additions and 111 deletions

View file

@ -231,31 +231,22 @@ filled_wield_def._mcl_wieldview_item = "mcl_maps:filled_map"
local mcl_skins_enabled = minetest.global_exists("mcl_skins")
local function player_base_to_node_id(base, colorspec, sex)
return base:gsub("%.", "") .. minetest.colorspec_to_colorstring(colorspec):gsub("#", "") .. sex
end
if mcl_skins_enabled then
local bases = mcl_skins.base
local base_colors = mcl_skins.base_color
-- Generate a node for every skin
for _, base in pairs(bases) do
for _, base_color in pairs(base_colors) do
local node_id = player_base_to_node_id(base, base_color, "male")
local texture = mcl_skins.make_hand_texture(base, base_color)
local male = table.copy(filled_wield_def)
male._mcl_hand_id = node_id
male.mesh = "mcl_meshhand.b3d"
male.tiles = {texture}
minetest.register_node("mcl_maps:filled_map_" .. node_id, male)
node_id = player_base_to_node_id(base, base_color, "female")
local list = mcl_skins.get_skin_list()
for _, skin in pairs(list) do
if skin.slim_arms then
local female = table.copy(filled_wield_def)
female._mcl_hand_id = node_id
female._mcl_hand_id = skin.id
female.mesh = "mcl_meshhand_female.b3d"
female.tiles = {texture}
minetest.register_node("mcl_maps:filled_map_" .. node_id, female)
female.tiles = {skin.texture}
minetest.register_node("mcl_maps:filled_map_" .. skin.id, female)
else
local male = table.copy(filled_wield_def)
male._mcl_hand_id = skin.id
male.mesh = "mcl_meshhand.b3d"
male.tiles = {skin.texture}
minetest.register_node("mcl_maps:filled_map_" .. skin.id, male)
end
end
else

View file

@ -21,31 +21,22 @@ local node_def = {
range = minetest.registered_items[""].range
}
local function player_base_to_node_id(base, colorspec, sex)
return base:gsub("%.", "") .. minetest.colorspec_to_colorstring(colorspec):gsub("#", "") .. sex
end
if mcl_skins_enabled then
local bases = mcl_skins.base
local base_colors = mcl_skins.base_color
-- Generate a node for every skin
for _, base in pairs(bases) do
for _, base_color in pairs(base_colors) do
local node_id = player_base_to_node_id(base, base_color, "male")
local texture = mcl_skins.make_hand_texture(base, base_color)
local male = table.copy(node_def)
male._mcl_hand_id = node_id
male.mesh = "mcl_meshhand.b3d"
male.tiles = {texture}
minetest.register_node("mcl_meshhand:" .. node_id, male)
node_id = player_base_to_node_id(base, base_color, "female")
local list = mcl_skins.get_skin_list()
for _, skin in pairs(list) do
if skin.slim_arms then
local female = table.copy(node_def)
female._mcl_hand_id = node_id
female._mcl_hand_id = skin.id
female.mesh = "mcl_meshhand_female.b3d"
female.tiles = {texture}
minetest.register_node("mcl_meshhand:" .. node_id, female)
female.tiles = {skin.texture}
minetest.register_node("mcl_meshhand:" .. skin.id, female)
else
local male = table.copy(node_def)
male._mcl_hand_id = skin.id
male.mesh = "mcl_meshhand.b3d"
male.tiles = {skin.texture}
minetest.register_node("mcl_meshhand:" .. skin.id, male)
end
end
else
@ -58,8 +49,7 @@ end
if mcl_skins_enabled then
-- Change the player's hand to their skin
mcl_skins.register_on_set_skin(function(player)
local data = mcl_skins.players[player:get_player_name()]
local node_id = player_base_to_node_id(data.base, data.base_color, data.slim_arms and "female" or "male")
local node_id = mcl_skins.get_node_id_by_player(player)
player:get_inventory():set_stack("hand", 1, "mcl_meshhand:" .. node_id)
end)
else

View file

@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2022 MrRar
Copyright (c) 2016 TenPlus1
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,11 +1,11 @@
# mcl_skins
# Mineclone Skins
This mod allows advanced skin customization.
Use the /skin command to open the skin configuration screen.
## License
Code under MIT license
Author: MrRar
Author: TenPlus1, Zeg9, MrRar
See image_credits.txt for image licensing.
@ -48,9 +48,18 @@ This parameter is optional. Must be a number. If it is not a valid page number t
Register a function to be called whenever a player skin changes.
The function will be given a player ObjectRef as a parameter.
### `mcl_skins.make_hand_texture(base, colorspec)`
Generate a texture string from a base texture and color.
This function is used by mods that want to have a first person hand textured like the player skin.
### `mcl_skins.get_skin_list()`
This function is used by mods that want a list of skins to register nodes that use the player skin as a texture.
Returns an array of tables containing information about each skin.
Each table contains the following properties:
`id`: A string representing the node ID. A node can be registered using this node ID.
`texture`: A texture string that can be used in the node defintion.
`slim_arms`: A boolean value. If true, this texture is used with the "female" player mesh. Otherwise the regular mesh is to be used.
### `mcl_skins.get_node_id_by_player(player)`
`player` is a player ObjectRef.
Returns a string node ID based on players current skin for use by mods that want to register nodes that use the player skin.
### `mcl_skins.save(player)`
Save player skin. `player` is a player ObjectRef.
@ -67,7 +76,7 @@ These colors are separate from `mcl_skins.color` because some mods register two
A table of ColorSpec integers that the player can select to color colorable skin items.
### `mcl_skins.players`
A table mapped by player name containing tables holding the player's selected skin items and colors.
A table mapped by player ObjectRef containing tables holding the player's selected skin items and colors.
Only stores skin information for logged in users.
### mcl_skins.compile_skin(skin)

View file

@ -2,6 +2,7 @@ local S = minetest.get_translator("mcl_skins")
local color_to_string = minetest.colorspec_to_colorstring
mcl_skins = {
simple_skins = {},
item_names = {"base", "footwear", "eye", "mouth", "bottom", "top", "hair", "headwear"},
tab_names = {"template", "base", "headwear", "hair", "eye", "mouth", "top", "arm", "bottom", "footwear"},
tab_descriptions = {
@ -14,7 +15,8 @@ mcl_skins = {
bottom = S("Bottoms"),
top = S("Tops"),
hair = S("Hairs"),
headwear = S("Headwears")
headwear = S("Headwears"),
skin = S("Skins"),
},
steve = {}, -- Stores skin values for Steve skin
alex = {}, -- Stores skin values for Alex skin
@ -70,10 +72,13 @@ function mcl_skins.register_item(item)
end
function mcl_skins.save(player)
local name = player:get_player_name()
local skin = mcl_skins.players[name]
local skin = mcl_skins.players[player]
if not skin then return end
player:get_meta():set_string("mcl_skins:skin", minetest.serialize(skin))
local meta = player:get_meta()
meta:set_string("mcl_skins:skin", minetest.serialize(skin))
meta:set_string("mcl_skins:skin_id", tostring(skin.simple_skins_id or ""))
end
minetest.register_chatcommand("skin", {
@ -82,26 +87,19 @@ minetest.register_chatcommand("skin", {
func = function(name, param) mcl_skins.show_formspec(minetest.get_player_by_name(name)) end
})
function mcl_skins.make_hand_texture(base, colorspec)
local output = ""
if mcl_skins.masks[base] then
output = mcl_skins.masks[base] ..
"^[colorize:" .. color_to_string(colorspec) .. ":alpha"
end
if #output > 0 then output = output .. "^" end
output = output .. base
return output
end
function mcl_skins.compile_skin(skin)
if skin.simple_skins_id then
return mcl_skins.simple_skins[skin.simple_skins_id].texture
end
local output = ""
for i, tab in pairs(mcl_skins.item_names) do
local texture = skin[tab]
for i, item in pairs(mcl_skins.item_names) do
local texture = skin[item]
if texture and texture ~= "blank.png" then
if skin[tab .. "_color"] and mcl_skins.masks[texture] then
if skin[item .. "_color"] and mcl_skins.masks[texture] then
if #output > 0 then output = output .. "^" end
local color = color_to_string(skin[tab .. "_color"])
local color = color_to_string(skin[item .. "_color"])
output = output ..
"(" .. mcl_skins.masks[texture] .. "^[colorize:" .. color .. ":alpha)"
end
@ -117,11 +115,17 @@ function mcl_skins.update_player_skin(player)
return
end
local skin = mcl_skins.players[player:get_player_name()]
local skin = mcl_skins.players[player]
mcl_player.player_set_skin(player, mcl_skins.compile_skin(skin))
local model = skin.slim_arms and "mcl_armor_character_female.b3d" or "mcl_armor_character.b3d"
local slim_arms
if skin.simple_skins_id then
slim_arms = mcl_skins.simple_skins[skin.simple_skins_id].slim_arms
else
slim_arms = skin.slim_arms
end
local model = slim_arms and "mcl_armor_character_female.b3d" or "mcl_armor_character.b3d"
mcl_player.player_set_model(player, model)
mcl_inventory.update_inventory_formspec(player)
@ -136,30 +140,34 @@ minetest.register_on_joinplayer(function(player)
local function table_get_random(t)
return t[math.random(#t)]
end
local name = player:get_player_name()
local skin = player:get_meta():get_string("mcl_skins:skin")
if skin then
skin = minetest.deserialize(skin)
end
if skin then
mcl_skins.players[name] = skin
mcl_skins.players[player] = skin
else
if math.random() > 0.5 then
skin = table.copy(mcl_skins.steve)
else
skin = table.copy(mcl_skins.alex)
end
mcl_skins.players[name] = skin
mcl_skins.players[player] = skin
end
mcl_skins.players[player].simple_skins_id = nil
if #mcl_skins.simple_skins > 0 then
local skin_id = tonumber(player:get_meta():get_string("mcl_skins:skin_id"))
if skin_id and mcl_skins.simple_skins[skin_id] then
mcl_skins.players[player].simple_skins_id = skin_id
end
end
mcl_skins.save(player)
mcl_skins.update_player_skin(player)
end)
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
if name then
mcl_skins.players[name] = nil
end
mcl_skins.players[player] = nil
end)
mcl_skins.registered_on_set_skins = {}
@ -169,7 +177,9 @@ function mcl_skins.register_on_set_skin(func)
end
function mcl_skins.show_formspec(player, active_tab, page_num)
active_tab = active_tab or "template"
local skin = mcl_skins.players[player]
local default = #mcl_skins.simple_skins > 0 and "skin" or "template"
active_tab = active_tab or default
page_num = page_num or 1
local page_count
@ -179,13 +189,16 @@ function mcl_skins.show_formspec(player, active_tab, page_num)
if page_num > page_count then
page_num = page_count
end
elseif active_tab == "skin" then
page_count = math.ceil((#mcl_skins.simple_skins + 2) / 8)
if page_num > page_count then
page_num = page_count
end
else
page_num = 1
page_count = 1
end
local player_name = player:get_player_name()
local skin = mcl_skins.players[player_name]
local formspec = "formspec_version[3]size[13.2,11]"
for i, tab in pairs(mcl_skins.tab_names) do
@ -197,17 +210,66 @@ function mcl_skins.show_formspec(player, active_tab, page_num)
local y = 0.3 + (i - 1) * 0.8
formspec = formspec ..
"button[0.3," .. y .. ";3,0.8;" .. tab .. ";" .. mcl_skins.tab_descriptions[tab] .. "]"
if skin.simple_skins_id then break end
end
local mesh = skin.slim_arms and "mcl_armor_character_female.b3d" or "mcl_armor_character.b3d"
local slim_arms
if skin.simple_skins_id then
slim_arms = mcl_skins.simple_skins[skin.simple_skins_id].slim_arms
else
slim_arms = skin.slim_arms
end
local mesh = slim_arms and "mcl_armor_character_female.b3d" or "mcl_armor_character.b3d"
formspec = formspec ..
"model[10,0.3;3,7;player_mesh;" .. mesh .. ";" ..
mcl_skins.compile_skin(skin) ..
",blank.png,blank.png;0,180;false;true;0,0;0]"
if active_tab == "skin" then
local page_start = (page_num - 1) * 8 - 1
local page_end = math.min(page_start + 8 - 1, #mcl_skins.simple_skins)
formspec = formspec ..
"style_type[button;bgcolor=#00000000]"
if active_tab == "template" then
local skin = table.copy(skin)
skin_id = skin.simple_skins_id or -1
skin.simple_skins_id = nil
local skins = table.copy(mcl_skins.simple_skins)
skins[-1] = {
slim_arms = skin.slim_arms,
texture = mcl_skins.compile_skin(skin),
}
for i = page_start, page_end do
local skin = skins[i]
local j = i - page_start - 1
local mesh = skin.slim_arms and "mcl_armor_character_female.b3d" or "mcl_armor_character.b3d"
local x = 3.5 + (j + 1) % 4 * 1.6
local y = 0.3 + math.floor((j + 1) / 4) * 3.1
formspec = formspec ..
"model[" .. x .. "," .. y .. ";1.5,3;player_mesh;" .. mesh .. ";" ..
skin.texture ..
",blank.png,blank.png;0,180;false;true;0,0;0]"
if skin_id == i then
formspec = formspec ..
"style[" .. i ..
";bgcolor=;bgimg=mcl_skins_select_overlay.png;" ..
"bgimg_pressed=mcl_skins_select_overlay.png;bgimg_middle=14,14]"
end
formspec = formspec ..
"button[" .. x .. "," .. y .. ";1.5,3;" .. i .. ";]"
end
if page_start == -1 then
formspec = formspec .. "image[3.85,1;0.8,0.8;mcl_skins_button.png]"
end
elseif active_tab == "template" then
formspec = formspec ..
"model[4,2;2,3;player_mesh;mcl_armor_character.b3d;" ..
mcl_skins.compile_skin(mcl_skins.steve) ..
@ -223,7 +285,7 @@ function mcl_skins.show_formspec(player, active_tab, page_num)
elseif mcl_skins[active_tab] then
formspec = formspec ..
"style_type[button;border=false;bgcolor=#00000000]"
"style_type[button;bgcolor=#00000000]"
local textures = mcl_skins[active_tab]
local page_start = (page_num - 1) * 16 + 1
local page_end = math.min(page_start + 16 - 1, #textures)
@ -264,19 +326,19 @@ function mcl_skins.show_formspec(player, active_tab, page_num)
if skin[active_tab] == texture then
formspec = formspec ..
"style[" .. texture .. ";border=false;bgcolor=#00000000]" ..
"image_button[" .. x .. "," .. y ..
";1.5,1.5;mcl_skins_select_overlay.png;" .. texture .. ";]"
else
formspec = formspec .. "button[" .. x .. "," .. y .. ";1.5,1.5;" .. texture .. ";]"
"style[" .. texture ..
";bgcolor=;bgimg=mcl_skins_select_overlay.png;" ..
"bgimg_pressed=mcl_skins_select_overlay.png;bgimg_middle=14,14]"
end
formspec = formspec .. "button[" .. x .. "," .. y .. ";1.5,1.5;" .. texture .. ";]"
end
elseif active_tab == "arm" then
local thick_overlay = not skin.slim_arms and "^mcl_skins_select_overlay.png" or ""
local slim_overlay = skin.slim_arms and "^mcl_skins_select_overlay.png" or ""
local x = skin.slim_arms and 4.7 or 3.6
formspec = formspec ..
"image_button[3.6,0.3;1,1;mcl_skins_thick_arms.png" .. thick_overlay ..";thick_arms;]" ..
"image_button[4.7,0.3;1,1;mcl_skins_slim_arms.png" .. slim_overlay ..";slim_arms;]"
"image_button[3.6,0.3;1,1;mcl_skins_thick_arms.png;thick_arms;]" ..
"image_button[4.7,0.3;1,1;mcl_skins_slim_arms.png;slim_arms;]" ..
"style[arm;bgcolor=;bgimg=mcl_skins_select_overlay.png;bgimg_middle=14,14;bgimg_pressed=mcl_skins_select_overlay.png]" ..
"button[" .. x .. ",0.3;1,1;arm;]"
end
@ -287,11 +349,6 @@ function mcl_skins.show_formspec(player, active_tab, page_num)
local tab_color = active_tab .. "_color"
local selected_color = skin[tab_color]
for i, colorspec in pairs(colors) do
local overlay = ""
if selected_color == colorspec then
overlay = "^mcl_skins_select_overlay.png"
end
local color = color_to_string(colorspec)
i = i - 1
local x = 3.6 + i % 6 * 0.9
@ -299,7 +356,15 @@ function mcl_skins.show_formspec(player, active_tab, page_num)
formspec = formspec ..
"image_button[" .. x .. "," .. y ..
";0.8,0.8;blank.png^[noalpha^[colorize:" ..
color .. ":alpha" .. overlay .. ";" .. colorspec .. ";]"
color .. ":alpha;" .. colorspec .. ";]"
if selected_color == colorspec then
formspec = formspec ..
"style[" .. color ..
";bgcolor=;bgimg=mcl_skins_select_overlay.png;bgimg_middle=14,14;bgimg_pressed=mcl_skins_select_overlay.png]" ..
"button[" .. x .. "," .. y .. ";0.8,0.8;" .. color .. ";]"
end
end
if not (active_tab == "base") then
@ -345,6 +410,7 @@ function mcl_skins.show_formspec(player, active_tab, page_num)
"label[6.3,7.2;" .. page_num .. " / " .. page_count .. "]"
end
local player_name = player:get_player_name()
minetest.show_formspec(player_name, "mcl_skins:" .. active_tab .. "_" .. page_num, formspec)
end
@ -367,11 +433,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if not page_num or not active_tab then return true end
page_num = math.floor(tonumber(page_num) or 1)
local player_name = player:get_player_name()
-- Cancel formspec resend after scrollbar move
if mcl_skins.players[player_name].form_send_job then
mcl_skins.players[player_name].form_send_job:cancel()
if mcl_skins.players[player].form_send_job then
mcl_skins.players[player].form_send_job:cancel()
end
if fields.quit then
@ -380,12 +444,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
if fields.alex then
mcl_skins.players[player_name] = table.copy(mcl_skins.alex)
mcl_skins.players[player] = table.copy(mcl_skins.alex)
mcl_skins.update_player_skin(player)
mcl_skins.show_formspec(player, active_tab, page_num)
return true
elseif fields.steve then
mcl_skins.players[player_name] = table.copy(mcl_skins.steve)
mcl_skins.players[player] = table.copy(mcl_skins.steve)
mcl_skins.update_player_skin(player)
mcl_skins.show_formspec(player, active_tab, page_num)
return true
@ -398,7 +462,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
local skin = mcl_skins.players[player_name]
local skin = mcl_skins.players[player]
if not skin then return true end
if fields.next_page then
@ -439,12 +503,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local color = 0xff000000 + red * 0x10000 + green * 0x100 + blue
if color >= 0 and color <= 0xffffffff then
-- We delay resedning the form because otherwise it will break dragging scrollbars
mcl_skins.players[player_name].form_send_job = minetest.after(0.2, function()
mcl_skins.players[player].form_send_job = minetest.after(0.2, function()
if player and player:is_player() then
skin[active_tab .. "_color"] = color
mcl_skins.update_player_skin(player)
mcl_skins.show_formspec(player, active_tab, page_num)
mcl_skins.players[player_name].form_send_job = nil
mcl_skins.players[player].form_send_job = nil
end
end)
return true
@ -459,6 +523,21 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
if field and active_tab == "skin" then
local skin_id = tonumber(field)
skin_id = skin_id and math.floor(skin_id) or 0
if
#mcl_skins.simple_skins > 0 and
skin_id >= -1 and skin_id <= #mcl_skins.simple_skins
then
if skin_id == -1 then skin_id = nil end
skin.simple_skins_id = skin_id
mcl_skins.update_player_skin(player)
mcl_skins.show_formspec(player, active_tab, page_num)
end
return true
end
-- See if field is a texture
if field and mcl_skins[active_tab] then
for i, texture in pairs(mcl_skins[active_tab]) do

View file

@ -1,4 +1,8 @@
local mod_path = minetest.get_modpath(minetest.get_current_modname())
local mcl_skins_enabled = minetest.settings:get_bool("mcl_enable_skin_customization", true)
if mcl_skins_enabled then dofile(mod_path .. "/edit_skin.lua") end
if mcl_skins_enabled then
dofile(mod_path .. "/edit_skin.lua")
dofile(mod_path .. "/simple_skins.lua")
dofile(mod_path .. "/mesh_hand.lua")
end

View file

@ -173,7 +173,8 @@
},
{
"type": "headwear",
"steve": true
"steve": true,
"alex": true
},
{
"type": "bottom",

View file

@ -1,4 +1,5 @@
# textdomain: mcl_skins
Skins=
Templates=
Arm size=
Bases=

View file

@ -0,0 +1,53 @@
local function make_texture(base, colorspec)
local output = ""
if mcl_skins.masks[base] then
output = mcl_skins.masks[base] ..
"^[colorize:" .. minetest.colorspec_to_colorstring(colorspec) .. ":alpha"
end
if #output > 0 then output = output .. "^" end
output = output .. base
return output
end
function mcl_skins.get_skin_list()
local list = {}
for _, base in pairs(mcl_skins.base) do
for _, base_color in pairs(mcl_skins.base_color) do
local id = base:gsub(".png$", "") .. minetest.colorspec_to_colorstring(base_color):gsub("#", "")
local female = {
texture = make_texture(base, base_color),
slim_arms = true,
id = id .. "_female"
}
table.insert(list, female)
local male = {
texture = make_texture(base, base_color),
slim_arms = false,
id = id .. "_male"
}
table.insert(list, male)
end
end
for _, skin in pairs(mcl_skins.simple_skins) do
table.insert(list, {
texture = skin.texture,
slim_arms = skin.slim_arms,
id = skin.texture:gsub(".png$", "") .. "_" .. (skin.slim_arms and "female" or "male"),
})
end
return list
end
function mcl_skins.get_node_id_by_player(player)
local skin = mcl_skins.players[player]
if skin.simple_skins_id then
local skin = mcl_skins.simple_skins[skin.simple_skins_id]
return skin.texture:gsub(".png$", "") ..
"_" .. (skin.slim_arms and "female" or "male")
else
return skin.base:gsub(".png$", "") ..
minetest.colorspec_to_colorstring(skin.base_color):gsub("#", "") ..
"_" .. (skin.slim_arms and "female" or "male")
end
end

View file

@ -0,0 +1,52 @@
local function init_simple_skins()
local id, f, data, skin = 0
local mod_path = minetest.get_modpath("mcl_skins")
while true do
if id == 0 then
skin = "character.png"
else
skin = "mcl_skins_character_" .. id .. ".png"
-- Does skin file exist?
f = io.open(mod_path .. "/textures/" .. skin)
-- escape loop if not found
if not f then
break
end
f:close()
end
local metafile
-- does metadata exist for that skin file ?
if id == 0 then
metafile = "mcl_skins_character.txt"
else
metafile = "mcl_skins_character_"..id..".txt"
end
f = io.open(mod_path .. "/meta/" .. metafile)
data = nil
if f then
data = minetest.deserialize("return {" .. f:read("*all") .. "}")
f:close()
end
-- add metadata to list
mcl_skins.simple_skins[id] = {
texture = skin,
slim_arms = data and data.gender == "female",
}
id = id + 1
end
if #mcl_skins.simple_skins > 0 then
table.insert(mcl_skins.tab_names, 1, "skin")
else
mcl_skins.simple_skins = {}
end
end
init_simple_skins()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

After

Width:  |  Height:  |  Size: 178 B