diff --git a/worldmods/multiskin/LICENSE b/worldmods/multiskin/LICENSE new file mode 100644 index 0000000..8864d4a --- /dev/null +++ b/worldmods/multiskin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/worldmods/multiskin/README.md b/worldmods/multiskin/README.md new file mode 100644 index 0000000..a92a724 --- /dev/null +++ b/worldmods/multiskin/README.md @@ -0,0 +1,51 @@ +[mod] Multiskin [multiskin] [0.1.1] +=================================== + +**Minetest Version:** 0.4.16 + +**Depends:** default + +Adds per-player support for version 1.8 minecraft skins. + +On its own this mod acts as a replacement for the out-dated player_textures +mod by PilzAdam. Individual player textures can be placed in the mod's +textures directory and will be automatically assigned to a player by naming +the texture file as `player_.png` + +This mod also supports the following skin-switching mods which can be used +in conjuction with the multiskin default skins and chat commands. +``` +[mod] Skins [skins] by Zeg9 +[mod] Simple Skins [simple_skins] by TenPlus1 +[mod] Unified Skins [u_skins] by SmallJoker +[mod] Wardrobe [wardrobe] by prestidigitator +``` +Note that auto skin format detection is only available for these mods if +the multiskin mod is listed in the `secure.trusted_mods` setting. + +Configuration +------------- + +You can set the default skin by including the following settings in your +minetest.conf + +Supported formats are `1.0` and `1.8` +``` +multiskin_skin = +multiskin_format = +``` +Chat Commands +------------- + +Requires `server` priv in multiplayer mode, no privs required in singleplayer. +``` +/multiskin format +/multiskin set +/multiskin unset +/multiskin help +``` +TODO +---- + +Document the api and add support to my 3d_armor and clothing mods. + diff --git a/worldmods/multiskin/depends.txt b/worldmods/multiskin/depends.txt new file mode 100644 index 0000000..13f19e4 --- /dev/null +++ b/worldmods/multiskin/depends.txt @@ -0,0 +1,2 @@ +default +sfinv? diff --git a/worldmods/multiskin/description.txt b/worldmods/multiskin/description.txt new file mode 100644 index 0000000..d905ff7 --- /dev/null +++ b/worldmods/multiskin/description.txt @@ -0,0 +1,2 @@ +Dual skin format player model. +Supports minecraft 1.0 and 1.8 skin formats on a per-player basis. diff --git a/worldmods/multiskin/init.lua b/worldmods/multiskin/init.lua new file mode 100644 index 0000000..a13a770 --- /dev/null +++ b/worldmods/multiskin/init.lua @@ -0,0 +1,264 @@ +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) +local dir_list = minetest.get_dir_list(modpath.."/textures") +local default_skin = minetest.setting_get("multiskin_skin") or "character.png" +local default_format = minetest.setting_get("multiskin_format") or "1.0" +local player_skins = {} +local player_format = {} +local player_textures = {} +local skin_previews = {} +local skin_format = {[default_skin]=default_format} + +local function 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 + +for _, fn in pairs(dir_list) do + local file = io.open(modpath.."/textures/"..fn, "rb") + if file then + skin_format[fn] = get_skin_format(file) + file:close() + end +end + +-- 3rd party skin-switcher support +-- may be removed from future versions as these mods do not +-- use the proper api method for setting player textures +-- +-- Auto skin format detection for 3rd party mods requires +-- that multiskin is included in 'trusted mods' + +local env = minetest.request_insecure_environment() +local skin_mod = modname +local skin_mods = {"skins", "u_skins", "simple_skins", "wardrobe"} +for _, mod in pairs(skin_mods) do + local path = minetest.get_modpath(mod) + if path then + local dir_list = minetest.get_dir_list(path.."/textures") + for _, fn in pairs(dir_list) do + if fn:find("_preview.png$") then + skin_previews[fn] = true + elseif env then + local file = env.io.open(path.."/textures/"..fn, "rb") + if file then + skin_format[fn] = get_skin_format(file) + file:close() + end + end + end + skin_mod = mod + end +end +env = nil + +local function get_player_skin(player) + local name = player:get_player_name() + if name then + local skin = nil + if skin_mod == "skins" or skin_mod == "simple_skins" then + skin = skins.skins[name] + elseif skin_mod == "u_skins" then + skin = u_skins.u_skins[name] + elseif skin_mod == "wardrobe" then + local skins = wardrobe.playerSkins or {} + if skins[name] then + return skins[name] + end + end + if skin then + return skin..".png" + end + for _, fn in pairs(dir_list) do + if fn == "player_"..name..".png" then + return fn + end + end + end + return default_skin +end + +multiskin = { + model = "multiskin.b3d", + skins = player_skins, + textures = player_textures, +} + +multiskin.set_player_skin = function(player, skin) + local name = player:get_player_name() + local format = skin_format[skin] + if format then + player_format[name] = format + player:set_attribute("multiskin_format", format) + end + player_skins[name].skin = skin + player:set_attribute("multiskin_skin", skin) +end + +multiskin.set_player_format = function(player, format) + local name = player:get_player_name() + player_format[name] = format + player:set_attribute("multiskin_format", format) +end + +multiskin.add_preview = function(texture) + skin_previews[texture] = true +end + +multiskin.get_preview = function(player) + local skin = player:get_attribute("multiskin_skin") + if skin then + local preview = skin:gsub(".png$", "_preview.png") + if skin_previews[preview] then + return preview + end + end +end + +multiskin.update_player_visuals = function(player) + local name = player:get_player_name() + if not name or not player_skins[name] then + return + end + local anim = default.player_get_animation(player) or {} + if anim.model == "character.b3d" then + default.player_set_model(player, multiskin.model) + elseif anim.model ~= multiskin.model then + return + end + local textures = player_textures[name] or {} + local skin = player_skins[name].skin or "blank.png" + local cape = player_skins[name].cape + local layers = {} + for k, v in pairs(player_skins[name]) do + if k ~= "skin" and k ~= "cape" then + table.insert(layers, v) + end + end + local overlay = table.concat(layers, "^") + local format = player_format[name] or default_format + if format == "1.8" then + if overlay ~= "" then + skin = skin.."^"..overlay + end + textures[1] = cape or "blank.png" + textures[2] = skin + else + if cape then + skin = skin.."^"..cape + end + if overlay == "" then + overlay = "blank.png" + end + textures[1] = skin + textures[2] = overlay + end + default.player_set_textures(player, table.copy(textures)) +end + +default.player_register_model("multiskin.b3d", { + animation_speed = 30, + textures = { + "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}, + }, +}) + +minetest.register_on_joinplayer(function(player) + minetest.after(0, function(player) + local name = player:get_player_name() + local skin = player:get_attribute("multiskin_skin") or + get_player_skin(player) + local anim = default.player_get_animation(player) or {} + player_textures[name] = anim.textures or {} + player_skins[name] = {skin=skin} + player_format[name] = player:get_attribute("multiskin_format") + multiskin.set_player_skin(player, skin) + multiskin.update_player_visuals(player) + end, player) +end) + +minetest.register_on_leaveplayer(function(player) + local name = player:get_player_name() + if name then + player_skins[name] = nil + player_format[name] = nil + player_textures[name] = nil + end +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local name = player:get_player_name() + for field, _ in pairs(fields) do + if string.find(field, "skins_set") then + minetest.after(0, function(player) + local name = player:get_player_name() + local skin = get_player_skin(player) + multiskin.set_player_skin(player, skin) + multiskin.update_player_visuals(player) + end, player) + end + end +end) + +minetest.register_chatcommand("multiskin", { + params = " [name] [args]", + description = "Multiskin player skin and format management", + func = function(name, param) + if not minetest.is_singleplayer() and + not minetest.check_player_privs(name, {server=true}) then + return false, "Insufficient privileges" + end + local cmd, player_name, args = string.match(param, "^([^ ]+) (.-) (.+)$") + if not args then + cmd, player_name = string.match(param, "([^ ]+) (.+)") + end + local player = nil + if player_name then + player = minetest.get_player_by_name(player_name) + else + cmd = string.match(param, "([^ ]+)") + end + if cmd == "help" then + local msg = "\nUsage: /multiskin [name] [args]\n\n".. + " format (1.0 or 1.8)\n".. + " set \n".. + " unset \n".. + " help (show this message)\n\n" + minetest.chat_send_player(name, msg) + elseif cmd == "format" and player and args then + multiskin.set_player_format(player, args) + multiskin.update_player_visuals(player) + elseif cmd == "set" and player and args then + multiskin.set_player_skin(player, args) + multiskin.update_player_visuals(player) + elseif cmd == "unset" and player then + player_skins[player_name].skin = get_player_skin(player) + player:set_attribute("multiskin_skin", nil) + multiskin.update_player_visuals(player) + else + return false, "Invalid parameters, see /multiskin help" + end + end, +}) diff --git a/worldmods/multiskin/mod.conf b/worldmods/multiskin/mod.conf new file mode 100644 index 0000000..502163d --- /dev/null +++ b/worldmods/multiskin/mod.conf @@ -0,0 +1 @@ +name = multiskin diff --git a/worldmods/multiskin/models/multiskin.b3d b/worldmods/multiskin/models/multiskin.b3d new file mode 100644 index 0000000..e5a68d5 Binary files /dev/null and b/worldmods/multiskin/models/multiskin.b3d differ diff --git a/worldmods/multiskin/models/multiskin.blend b/worldmods/multiskin/models/multiskin.blend new file mode 100644 index 0000000..22281b4 Binary files /dev/null and b/worldmods/multiskin/models/multiskin.blend differ diff --git a/worldmods/multiskin/textures/skins_here.txt b/worldmods/multiskin/textures/skins_here.txt new file mode 100644 index 0000000..0a667b8 --- /dev/null +++ b/worldmods/multiskin/textures/skins_here.txt @@ -0,0 +1 @@ +Place your player skins here named as `player_.png`