Enhance player_api by skin methods

This commit is contained in:
Alexander Weber 2020-05-25 07:45:53 +02:00
parent 268f869e67
commit e14e8d3340
5 changed files with 236 additions and 45 deletions

View file

@ -8,17 +8,45 @@ player_api = {}
local animation_blend = 0 local animation_blend = 0
player_api.registered_models = { } player_api.registered_models = { }
player_api.registered_skins = { }
-- Local for speed. -- Local for speed.
local models = player_api.registered_models local models = player_api.registered_models
local skins = player_api.registered_skins
local registered_skin_modifiers = {}
local registered_on_skin_change = {}
function player_api.register_model(name, def) function player_api.register_model(name, def)
-- compatibility defaults
def.visual = def.visual or "mesh"
def.visual_size = def.visual_size or {x = 1, y = 1}
if def.visual == "mesh" and not def.mesh then
def.mesh = name
end
def.collisionbox = def.collisionbox or {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}
def.stepheight = def.stepheight or 0.6
def.eye_height = def.eye_height or 1.47
models[name] = def models[name] = def
end end
function player_api.register_skin(name, def)
def.name = name
skins[name] = def
end
function player_api.register_skin_modifier(modifier_func)
table.insert(registered_skin_modifiers, modifier_func)
end
function player_api.register_on_skin_change(modifier_func)
table.insert(registered_on_skin_change, modifier_func)
end
-- Player stats and animations -- Player stats and animations
local player_model = {} local player_model = {}
local player_textures = {} local player_textures = {}
local skin_textures = {}
local player_skin = {}
local player_anim = {} local player_anim = {}
local player_sneak = {} local player_sneak = {}
player_api.player_attached = {} player_api.player_attached = {}
@ -28,47 +56,165 @@ function player_api.get_animation(player)
return { return {
model = player_model[name], model = player_model[name],
textures = player_textures[name], textures = player_textures[name],
skin_textures = skin_textures[name],
animation = player_anim[name], animation = player_anim[name],
} }
end end
-- Called when a player's appearance needs to be updated -- Called when a player's appearance needs to be updated
function player_api.set_model(player, model_name) function player_api.set_model(player, model_name)
local default_model = models[player_api.default_model]
local name = player:get_player_name() local name = player:get_player_name()
local model = models[model_name] local model = model_name and models[model_name]
if model then if not model then
if player_model[name] == model_name then model_name = player_api.default_model
return model = default_model
end
player:set_properties({
mesh = model_name,
textures = player_textures[name] or model.textures,
visual = "mesh",
visual_size = model.visual_size or {x = 1, y = 1},
collisionbox = model.collisionbox or {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3},
stepheight = model.stepheight or 0.6,
eye_height = model.eye_height or 1.47,
})
player_api.set_animation(player, "stand")
else
player:set_properties({
textures = {"player.png", "player_back.png"},
visual = "upright_sprite",
visual_size = {x = 1, y = 2},
collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.75, 0.3},
stepheight = 0.6,
eye_height = 1.625,
})
end end
player:set_properties({
mesh = model.mesh,
textures = player_textures[name] or model.textures,
visual = model.visual,
visual_size = model.visual_size,
collisionbox = model.collisionbox,
stepheight = model.stepheight,
eye_height = model.eye_height,
})
player_api.set_animation(player, "stand")
player_model[name] = model_name player_model[name] = model_name
end end
function player_api.set_textures(player, textures) function player_api.set_textures(player, textures)
local name = player:get_player_name() local name = player:get_player_name()
local model = models[player_model[name]] local model = models[player_model[name]]
local model_textures = model and model.textures or nil local skin = skins[player_skin[name]]
player_textures[name] = textures or model_textures
player:set_properties({textures = textures or model_textures,}) local textures
if skin.textures then
textures = table.copy(skin.textures)
skin_textures[name] = skin.textures
elseif skin.texture then
textures = { skin.texture }
skin_textures[name] = { skin.texture }
else
textures = table.copy(model.textures)
skin_textures[name] = model.textures
end
for _, modifier_func in ipairs(registered_skin_modifiers) do
textures = modifier_func(textures, player, player_model[name], player_skin[name]) or textures
end
if model.skin_modifier then
textures = model:skin_modifier(textures, player, player_model[name], player_skin[name]) or textures
end
player_textures[name] = textures
player:set_properties({textures = textures})
end
-- Called when a player's skin is changed
function player_api.set_skin(player, skin_name, is_default, is_force)
local name = player:get_player_name()
local skin = skins[skin_name]
if not skin then
skin_name = player_api.default_skin
skin = skins[skin_name]
is_default = true
end
if player_skin[name] == skin_name and not is_force then
return
end
-- Handle skin model
player_api.set_model(player, skin.model_name)
-- Handle skin textures
player_skin[name] = skin_name
player_api.set_textures(player)
if not is_default then
player:set_attribute("player_api:skin", skin_name)
end
for _, modifier_func in ipairs(registered_on_skin_change) do
modifier_func(player, player_model[name], skin_name)
end
end
-- Get current assigned or default skin for player
function player_api.get_skin(player)
local assigned_skin = player:get_attribute("player_api:skin")
if assigned_skin then
return assigned_skin, false
end
local skinname = "player_"..player:get_player_name():lower()
if player_api.registered_skins[skinname] then
return skinname, true
end
return player_api.default_skin, true
end
local textures_skin_suffix_blacklist = {
preview = true,
back = true
}
player_api.textures_skin_suffix_blacklist = textures_skin_suffix_blacklist
-- Read and analyze data in textures and metadata folder and register them
function player_api.read_textures_and_meta(hook)
local modpath = minetest.get_modpath(minetest.get_current_modname())
for _, fn in pairs(minetest.get_dir_list(modpath..'/textures/')) do
local nameparts = fn:sub(1, -5):split("_")
local prefix = nameparts[1]
if ( prefix == 'player' and nameparts[2] or prefix == 'character' ) then
if not textures_skin_suffix_blacklist[nameparts[#nameparts]] then
local skin = {texture = fn}
local skin_id = table.concat(nameparts,'_')
-- get metadata from file
local file = io.open(modpath.."/meta/"..skin_id..".txt", "r")
if file then
local data = minetest.deserialize("return {" .. file:read('*all') .. "}")
file:close()
if data then
for k, v in pairs(data) do
skin[k] = v
end
if data.name and not data.description then -- name is reserved for registration skin_id
skin.description = data.name
end
end
end
-- Check if preview exists
local file2 = io.open(modpath.."/textures/"..skin_id.."_preview.png", "r")
if file2 then
file2:close()
skin.preview = skin_id.."_preview.png"
end
-- Check for private
if prefix == "player" then
skin.playername = nameparts[2]
end
if not skin.description then
if nameparts[2] then
table.remove(nameparts, 1)
end
skin.description = table.concat(nameparts,' ')
end
-- process hook
if hook then
hook(modpath..'/textures/'..fn, skin)
end
player_api.register_skin(skin_id, skin)
end
end
end
end end
function player_api.set_animation(player, anim_name, speed) function player_api.set_animation(player, anim_name, speed)
@ -77,7 +223,7 @@ function player_api.set_animation(player, anim_name, speed)
return return
end end
local model = player_model[name] and models[player_model[name]] local model = player_model[name] and models[player_model[name]]
if not (model and model.animations[anim_name]) then if not (model and model.animations and model.animations[anim_name]) then
return return
end end
local anim = model.animations[anim_name] local anim = model.animations[anim_name]
@ -85,11 +231,30 @@ function player_api.set_animation(player, anim_name, speed)
player:set_animation(anim, speed or model.animation_speed, animation_blend) player:set_animation(anim, speed or model.animation_speed, animation_blend)
end end
function player_api.init_on_joinplayer(player)
player_api.player_attached[player:get_player_name()] = false
player_api.set_skin(player, player_api.get_skin(player), true, true)
player:set_local_animation(
{x = 0, y = 79},
{x = 168, y = 187},
{x = 189, y = 198},
{x = 200, y = 219},
30
)
end
minetest.register_on_joinplayer(function(player)
-- Wrapped call to be able to redefine the init function
player_api.init_on_joinplayer(player)
end)
minetest.register_on_leaveplayer(function(player) minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name() local name = player:get_player_name()
player_model[name] = nil player_model[name] = nil
player_anim[name] = nil player_anim[name] = nil
player_textures[name] = nil player_textures[name] = nil
skin_textures[name] = nil
player_skin[name] = nil
end) end)
-- Localize for better performance. -- Localize for better performance.

View file

@ -5,7 +5,7 @@ dofile(minetest.get_modpath("player_api") .. "/api.lua")
-- Default player appearance -- Default player appearance
player_api.register_model("character.b3d", { player_api.register_model("character.b3d", {
animation_speed = 30, animation_speed = 30,
textures = {"character.png", }, textures = {"character.png"},
animations = { animations = {
-- Standard animations. -- Standard animations.
stand = {x = 0, y = 79}, stand = {x = 0, y = 79},
@ -15,20 +15,24 @@ player_api.register_model("character.b3d", {
walk_mine = {x = 200, y = 219}, walk_mine = {x = 200, y = 219},
sit = {x = 81, y = 160}, sit = {x = 81, y = 160},
}, },
collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3},
stepheight = 0.6,
eye_height = 1.47,
}) })
-- Update appearance when the player joins player_api.register_model("upright_sprite", {
minetest.register_on_joinplayer(function(player) textures = {"player.png", "player_back.png"},
player_api.player_attached[player:get_player_name()] = false visual = "upright_sprite",
player_api.set_model(player, "character.b3d") visual_size = {x = 1, y = 2},
player:set_local_animation( collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.75, 0.3},
{x = 0, y = 79}, eye_height = 1.625,
{x = 168, y = 187}, })
{x = 189, y = 198},
{x = 200, y = 219}, player_api.read_textures_and_meta()
30
) player_api.register_skin("sprite", {
end) description = "Demo sprite player",
textures = { "player.png", "player_back.png" },
model_name = "upright_sprite",
preview = "player.png"
})
player_api.default_model = "character.b3d"
player_api.default_skin = "character"

View file

@ -0,0 +1,2 @@
name = "Sam",
author = "Jordach",

View file

@ -0,0 +1,20 @@
## The registered skin requires at least one texture file that can appiled to player.
name Skin technical name as registered (always set by player_api.register_skin())
model_name Player model to be used with the skin. If not set, the default model is used
texture Single file texture, will be used as `textures = { texture }`
textures Skin textures table
## Other mods require or use additional attirubes that can be added in registration
format Skins format. "1.0" (default) or "1.8"
## formspecs related
description Descriptive skin name to be shown in formspecs
preview Skin preview image to be shown in formspecs
author Skin author to be shown in formspecs
license Skin texture license to be shown in formspecs
## Skins list related
playername Private skin, to be used only by given player
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 Sort order in skins lists. If not given, the skin name or key is used

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB