-- =================================================================== -- The Unified Inventory part of this code is: -- Copyright (c) 2012 CoRNeRNoTe, Dean Montgomery -- License: GPLv3 -- As this module incorporates GPL3 code, the entire module is GPL3. -- However, the rest of the code is (c) 2017-2021 OldCoder (Robert -- Kiraly) and Poikilos (Jake Gustafson). -- =================================================================== -- "coderskins.fallback" should specify a 64x32 (not 64x64) fallback -- skin PNG file that's guaranteed to exist. local modname = minetest.get_current_modname() local modpath = minetest.get_modpath (modname) local storage = minetest.get_mod_storage() coderskins = {} coderskins.pages = {} coderskins.fallback = "player_character.png" coderskins.textdir = modpath .. "/textures/" -- =================================================================== -- This table maps a player name to an overlay name or list if, and -- only if, a skin has been displayed for the player and the associa- -- ted overlay(s) was/were added (or an attempt was made to add it/ -- them) in the most recent visual update. In all other cases, the map -- result is nil. -- Presently, possible overlay names include: -- -- musicbadge -- for the music badge -- ncmask -- for the ncvirus mask -- spacesuit -- for the spacesuit coderskins.overlay_name = {} coderskins.force_update = false -- =================================================================== dofile (modpath .. "/skinlist.lua" ) dofile (modpath .. "/oldmap.lua" ) -- =================================================================== local cslog = function (s) if false then ocutil.log ("[coderskins] " .. s) end end -- =================================================================== coderskins.png_exists = function (str) return ocutil.png_exists (modname, str) end -- =================================================================== coderskins.png_missing = function (str) return ocutil.png_missing (modname, str) end -- =================================================================== local get_stored_trbase = function (plname) if ocutil.str_empty (plname) then ocutil.panic ("get_stored_trbase") end local trbase = storage:get_string (plname) if ocutil.str_empty (trbase) then return "" end return trbase end -- =================================================================== local get_stored_skinpng = function (plname) local trbase = get_stored_trbase (plname) if ocutil.str_empty (trbase) then return "" end return trbase .. ".png" end -- =================================================================== local set_stored_trbase = function (plname, trbase) if ocutil.str_empty (plname ) or ocutil.str_empty (trbase ) then ocutil.panic ("set_stored_trbase") end trbase = trbase:gsub ("%.png$", "") if storage:get_string (plname) ~= trbase then storage:set_string (plname, trbase) end end -- =================================================================== for plname, trbase in pairs (coderskins.oldmap) do local stored = get_stored_trbase (plname) if ocutil.str_empty (stored) then local skinpng = trbase .. ".png" if coderskins.png_exists (skinpng) then set_stored_trbase (plname, trbase) end end end -- =================================================================== local function wstrim (s) return s:match "^%s*(.-)%s*$" end -- =================================================================== coderskins.get_player_skin = function (plname) local fallback = coderskins.fallback local gps = "coderskins.get_player_skin" local skinpng = get_stored_skinpng (plname) cslog (plname .. ": stored skinpng is " .. skinpng) if skinpng == "" or coderskins.png_missing (skinpng) then ocutil.log ("coderskins error: missing " .. skinpng) skinpng = fallback end if coderskins.png_missing (skinpng) then ocutil.panic (gps .. " #1: missing " .. skinpng) end local pngpath = coderskins.textdir .. skinpng local w, h = ocutil.get_png_dim (pngpath) if w == nil or w ~= 64 or h == nil or (h ~= 32 and h ~= 64) then cslog (plname .. ": ocutil.get_png_dim failed") if skinpng == fallback then ocutil.panic (gps .. " #2") end return fallback, 32 end cslog (plname .. ": found and using file " .. skinpng) return skinpng, h end -- =================================================================== coderskins.get_player_skin32 = function (plname) local skinpng = coderskins.get_player_skin (plname) return "[combine:64x32:0,0=" .. skinpng, 32 end -- =================================================================== local get_overlaypng = function (overlay_name) if ocutil.ends_with (overlay_name, ".png") then return overlay_name end return "coderskins_" .. overlay_name .. "_overlay.png" end -- =================================================================== -- This is a list of Lua patterns (not shell globs) to be applied to a -- PNG-file name. If there is a match, the PNG file is treated as tho- -- ugh it's likely to be transparent. coderskins.blanks = { ".*transparent.*" , ".*trans%.png" } -- =================================================================== -- In "coderskins.get_first_texture": -- "iostr" (short for "image_or_overlay_string") is a string that con- -- tains one or more image-file names (including filename extensions) -- and/or "^[" MT Lua API operation strings. -- At least one non-transparent image file must be specified. -- This function returns the name (including filename extension) of -- the first non-transparent image file. -- The name in question must come before the first "^[" operation str- -- ing (if any) in "iostr". Additionally, the "^[" operations (if any) -- shouldn't change the aspect ratio (i.e., ratio of width to height) -- of the image in question. -- The purpose of this routine is to simplify higher-level code that -- depends on aspect ratios. -- This routine is recursive. "debug_indent" should be omitted (or -- nil) in the highest-level call. It's used internally by the rou- -- tine in subsequent recursive calls. -- =================================================================== coderskins.get_first_texture = function (iostr, debug_indent) if debug_indent == nil then debug_indent = "" end local ender_i = iostr:find ("%^") -- ^ "The character % works as an escape character" -- - if ender_i ~= nil then if ender_i > 1 then local filename = iostr:sub (1, ender_i - 1) if ocutil.match_any_pattern (iostr, coderskins.blanks) ~= nil then -- 210916 Poikilos says: A transparent texture here is irrelevant (and -- is often a random size and ratio). cslog ("" .. debug_indent .. "- \"" .. iostr .. "\" so instead getting image size from: " .. iostr:sub (ender_i+1)) return coderskins.get_first_texture (iostr:sub (ender_i+1), debug_indent .. " ") end cslog ("" .. "- first texture (not transparent): " .. iostr:sub (1, ender_i)) return filename else cslog ("" .. "The image couldn't be detected" .. " since the overlay \"" .. iostr .. "\" started with ^ (at " .. ender_i .. ") not an image.") end end return iostr end -- =================================================================== -- In "coderskins.find_texture_size": -- "filename" is a string in the same format as the "iostr" parameter -- used by "coderskins.get_first_texture". -- An additional rule is imposed. The name of the first non-transpar- -- ent image file in the string must be in the following format: -- MT "mod" name followed by underscore, a descriptive string, and -- finally a filename extension (such as ".png"). The "mod" name it- -- self can include underscores but shouldn't start or end with them. -- Example: coderskins_ncmask_overlay.png -- The "mod" in question must have been loaded prior to the point -- where this function is called. -- This function returns a 2-element list. The list consists of the -- width and height of the image discussed above or nil, nil if some- -- thing does wrong. -- =================================================================== coderskins.find_texture_size = function (filename) filename = coderskins.get_first_texture (filename) local this_mod_path = ocutil.modpath_from_filename (filename) if this_mod_path ~= nil then local tmp_w, tmp_h = nil, nil if this_mod_path ~= nil then tmp_w, tmp_h = ocutil.get_png_dim (this_mod_path .. "/textures/" .. filename) if tmp_w ~= nil then return tmp_w, tmp_h else cslog ("" .. "\"" .. this_mod_path .. "/textures/\" didn't contain \"" .. filename .. "\"") end else cslog ("" .. "The filename \"" .. this_mod_path .. "\" isn't prefixed with a mod name, " .. "so the texture path can't be detected.") end tmp_w, tmp_h = ocutil.get_png_dim (coderskins.textdir .. filename) if tmp_w ~= nil then return tmp_w, tmp_h end end return nil, nil end -- =================================================================== -- If the specified player is online, this routine updates the dis- -- played skin to match the recorded one. -- "overlay_name" may be an overlay name, a list of such names, or -- nil. Possible overlay names are listed earlier in this file. -- If it's a list of such names, the names must be in canonical order -- as defined in the "default" mod. -- If it's a name or a list, this routine attempts to add the speci- -- fied overlay(s) to the skin displayed. -- "coderskins.overlay_name" maps a player name to an overlay name (or -- list) if and only if such an attempt was made in the most recent -- update for the player in question. -- =================================================================== -- 210906 RJK and Poikilos: Changes to this routine: -- -- Previously, if an overlay was applied to a square skin, this code -- dropped the lower half of the skin. This was a kludge. We've dele- -- ted that code. -- -- Additionally, this code has been reworked to support various com- -- binations of skin shape and overlay shape: -- -- rectangle and rectangle -- typically 64x32 and 64x32 -- square and rectangle -- typically 64x64 and 64x32 -- rectangle and square -- typically 64x32 and 64x64 -- square and square -- typically 64x64 and 64x64 -- Note: The use of the word "overlay" in this code is a simplifica- -- tion. In fact, images are not directly "overlaid" on top of each -- other but the net effect is similar for pieces of CoderSkins ar- -- mor. -- =================================================================== -- To explain further: -- A 3D model typically has an associated "materials" array. Each mat- -- terials array contains one or more "material" entries. We'll refer -- to such entries as "materials" for short. -- Early "materials" stored color, "shininess", "roughness", and "al- -- pha". These days, they store a number of additional properties as -- well. -- A "material ID" is an index in the materials array. Note: The first -- index may be 0, 1, or something else depending on the platforms us- -- ed. -- Each polygon in a model is assigned a material ID. Note: Many poly- -- gons may share the same material ID. -- =================================================================== -- There's a face-list which defines the polygons that are to be used -- in a given model. The face-list doesn't directly specify the loca- -- tions of the polygons in 3-space. It references external vertex da- -- ta which stores that information. -- Each face-list entry typically stores one material ID and 3 or more -- vertex IDs (where each vertex ID identifies the 3-space location of -- one vertex in the vertex data). -- =================================================================== -- In the 1980s, a supplemental mechanism known as "UV maps" evolved. -- The "UV maps" approach works like this: -- A 3D model typically has one or more "model texture" images. Each -- may contain multiple types of practical textures (such as "brick" -- or "grass") combined to produce a single larger image. -- The textures may be organized, in each model texture image, in dif- -- ferent ways. It isn't necessarily as simple as paste a set of -- square practical textures together to produce a larger model tex- -- ture. -- A 3D model typically has one or more "UV maps". Each UV map is a -- definitions table that lists sets of 2D polygons. -- In the modern era, "materials" entries specify exactly one UV map. -- So, a face-list entry indirectly specifies one UV map by way of the -- materials ID that it [the face-list entry] stores. -- The 2D-space polygons stored at the UV-map level need to line up -- with the 3D-space polygons stored at the face-list and vertex-data -- level to the following extent: -- -- The number of polygons needs to be the same. -- The side count for corresponding polygons needs to be the same. -- Corresponding vertexes need to be listed in the same order. -- A UV map is used as follows: Sub-regions in a particular shape -- associated with the UV map are copied out of the model texture -- associated with the UV map. One sub-region is copied for each of -- the 2D-space polygons in the UV map. It's used to fill the corre- -- sponding 3D-space polygon. -- =================================================================== -- There may be multiple UV maps. This is due to the fact that each UV -- map is designed to select texture image subregions of a different -- shape and a particular model may need to use multiple shapes in -- this context. -- =================================================================== -- "UV maps" aren't absolutely required but their use became common -- after the 1980s. Poikilos cites the rise of CGI in movies starting -- in that decade as a turning point: -- "For anything that wasn't supposed to look abstract, UV maps were -- pretty much necessary." -- The most advanced use of non-UV map models in movies was probably -- in the 1982 film "Tron". You generally need to use UV-maps for any- -- thing much beyond "Tron" that needs to fit visually into a concrete -- world." -- "Young Sherlock Holmes" (1985) was an early example of the use of -- UV maps in CGI. The stained-glass creature in that film was model- -- ed using this mechanism." -- =================================================================== -- The older Minetest 3D character models were based on just a single -- material ID. It allowed the use of a single UV map to put a "skin" -- on a character. -- In 2013, "StuJones11" created a "3D Armor" mod that used 3 material -- IDs: -- -- ID #1 or "pngim_rect": The model texture image used is a 2:1 ratio -- image (such as 64x32). It defines a low-resolution skin similar to -- old Minecraft skins. -- -- ID #2 or "pngim_armor: The model texture image used is a 2:1 ratio -- image (such as 64x32) again. It's used in conjunction in a 64x32 -- skin and adds armor. -- Some types of armor go, in a type-2 texture, in the same place as -- the corresponding body parts in the 64x32 skin. Other types [such -- as shields] go at unique reserved locations in the overlay. -- ID #3 or "pngim_wield": The model texture image used is a 1:1 ratio -- image (typically 16x16). -- Note: "pngim_wield" is different than the Minetest API's "wield- -- item" feature. "wielditem" only applies to the simplified HUD ver- -- sion of the character model that is part of the Minetest core en- -- gine. -- Sizes larger than 16x16 may have a negative effect on the "wield- -- item" feature. -- In 2017, Jordach aka Maciej Kasatkin implemented a material that he -- assigned material ID #2 to. This conflicted with the StuJones11 ID -- set and so Jordach's change wasn't compatible with 3D Armor. Jor- -- dach's ID #2 material was as follows: -- -- ID #2: The model texture image used was a 1:1 ratio image (such as -- 64x64). It defined a high-resolution skin similar to newer Mine- -- craft skins. -- OldCoder subsequently made 3D Armor and Jordach's enhanced skins -- work together through the simple kludge of truncating 64x64 skins -- to 64x32 when players put on items 3D Armor. This sacrificed de- -- tails, of course. -- In 2021, Poikilos moved Jordach's ID #2 to ID #4 and made various -- changes to Bucket Game and 3D Armor. The result was a MT "_game" -- that supported all 4 "material" types as well as both MT 4 and MT 5 -- clients at the same time. -- =================================================================== coderskins.change_skin = function (plname, overlay_name) coderskins.force_update = false cslog ("change_skin(" .. plname .. ", ...)...") local player = minetest.get_player_by_name (plname) if player == nil then return end local show_overlay = false if overlay_name ~= nil then show_overlay = true end local skinpng = coderskins.get_player_skin (plname) local pngpath = coderskins.textdir .. skinpng local skin_width, skin_height = ocutil.get_png_dim (pngpath) local pngim_rect = "" local pngim_armor = "" local pngim_wield = "" local pngim_square = "" if skin_width == nil then cslog ("Invalid PNG file: " .. pngpath) return end if skin_width / skin_height == 2 then pngim_rect = skinpng elseif skin_width / skin_height == 1 then pngim_square = skinpng end if show_overlay then if type (overlay_name) ~= "table" then overlay_name = { overlay_name } else overlay_name = ocutil.sort_first (overlay_name, "3d_armor" ) overlay_name = ocutil.sort_last (overlay_name, "badge" ) end for _, oname in ipairs (overlay_name) do local this_o = get_overlaypng (oname) local this_w, this_h = coderskins.find_texture_size (this_o) if this_w ~= nil then if this_w / this_h == 2 then pngim_armor = ocutil.combine_images (pngim_armor, this_o) cslog ("- overlaying \"" .. this_o .. "\" onto pngim_armor \"" .. pngim_armor .. "\"") elseif this_w / this_h == 1 then pngim_square = ocutil.combine_images (pngim_square, this_o) cslog ("- overlaying \"" .. this_o .. "\" onto pngim_square \"" .. pngim_square .. "\"") end else cslog ("Invalid PNG file: " .. this_o) end end end coderskins.overlay_name [plname] = overlay_name if rawget (_G, "armor") then pngim_wield = armor.textures [plname].wielditem if pngim_wield == nil then pngim_wield = "" end end local pt = "ptextures_transparent.png" if pngim_rect == "" then pngim_rect = pt end if pngim_armor == "" then pngim_armor = pt end if pngim_wield == "" then pngim_wield = pt end if pngim_square == "" then pngim_square = pt end cslog ("" .. plname .. " pngim_rect:" .. pngim_rect .. " pngim_armor:" .. pngim_armor .. " pngim_wield:" .. pngim_wield .. " pngim_square:" .. pngim_square) player:set_properties ({ textures = { pngim_rect , pngim_armor , pngim_wield , pngim_square } }) end -- =================================================================== local labmap = {} labmap ["09sharkboy" ] = "skin_09SharkBoy" labmap ["athena" ] = "skin_Athena" labmap ["brianna" ] = "skin_Brianna_Playz" labmap ["cat" ] = "skin_Actual_Cat" labmap ["chad" ] = "skin_Chad_Wild_Clay" labmap ["chara" ] = "skin_Chara" labmap ["cola" ] = "skin_Cola" labmap ["corona" ] = "skin_Corona" labmap ["cylon" ] = "skin_Gold_Cylon" labmap ["duck" ] = "skin_Duck" labmap ["elsa" ] = "skin_Queen_Elsa" labmap ["hotdog" ] = "skin_Hotdog" labmap ["kata" ] = "skin_Kata_Red_Hair" labmap ["kermit" ] = "skin_Business_Kermit" labmap ["ldshadow" ] = "skin_LDShadow_Lady" labmap ["luigi" ] = "skin_Luigi" labmap ["mario" ] = "skin_Mario" labmap ["moose" ] = "skin_MooseCraft" labmap ["offwhite" ] = "skin_Chinese_Off_White" labmap ["pizzagirl" ] = "skin_Pizza_Girl" labmap ["polarbear" ] = "skin_Polar_Bear" labmap ["potion" ] = "skin_Potion_Master" labmap ["preston" ] = "skin_Preston_Playz" labmap ["purple" ] = "skin_Purple_Outfit" labmap ["robocop" ] = "skin_Robocop" labmap ["rosalyn" ] = "skin_Rosalyn_Potion_Master" labmap ["stampy" ] = "skin_Stampy_Boots" labmap ["steampunk" ] = "skin_Steampunk" labmap ["superjack" ] = "skin_Super_Jack" labmap ["tiger" ] = "skin_Tiger" labmap ["tree" ] = "skin_Apple_Tree" -- =================================================================== for skin_label, trbase in pairs (labmap) do local skinpng = trbase .. ".png" if not coderskins.png_exists (skinpng) then cslog ("Dropping skin label " .. skin_label) labmap [skin_label] = nil end end local label_to_trbase = labmap local trbase_to_label = ocutil.swap_key_value (labmap) local sorted_labels = ocutil.ktable_to_vtable (labmap) labmap = nil -- =================================================================== for key, value in pairs (sorted_labels ) do cslog ("sorted_labels: " .. key .. " => " .. value) end for key, value in pairs (trbase_to_label ) do cslog ("trbase_to_label: " .. key .. " => " .. value) end -- =================================================================== local prompt = "Use e.g. /skin superjack" .. " to choose one of these:" -- =================================================================== local param_skins = { description = "list or change your own skins" , params = "skin name (optional)" , privs = {} , func = function (plname, params) local print_prompt = true local skin_label = params local trbase if ocutil.str_empty (skin_label) then skin_label = "" end skin_label = skin_label:gsub ("%.png$", "") if ocutil.str_empty (skin_label) then trbase = get_stored_trbase (plname) if ocutil.str_nonempty (trbase) then cslog ("player " .. plname .. " has trbase " .. trbase) local old_label = trbase_to_label [trbase] if ocutil.str_empty (old_label) then old_label = trbase end if ocutil.str_nonempty (old_label) then print_prompt = false minetest.chat_send_player (plname, "Presently, your skin is " .. old_label .. ". " .. prompt) end end if print_prompt then minetest.chat_send_player (plname, prompt) end local buf = "" local npline = 13 local noline = 0 for _, skin_label in pairs (sorted_labels) do buf = buf .. " " .. skin_label noline = noline + 1 if noline >= npline then minetest.chat_send_player (plname, buf) buf = "" noline = 0 end end if noline > 0 then minetest.chat_send_player (plname, buf) end return end trbase = label_to_trbase [skin_label] if trbase == nil then local xp = skin_label:gsub ("%.png$", "") .. ".png" if coderskins.png_missing (xp) then xp = "skin_" .. xp if coderskins.png_missing (xp) then xp = xp:gsub ("^skin_", "player_") if coderskins.png_missing (xp) then minetest.chat_send_player (plname, "Not a valid skin label") return end end end trbase = xp:gsub ("%.png$", "") end if coderskins.png_missing (trbase .. ".png") then minetest.chat_send_player (plname, "Sorry, PNG file is missing") return end local oname = coderskins.overlay_name [plname] set_stored_trbase (plname, trbase) coderskins.change_skin (plname, oname) minetest.chat_send_player (plname, "O.K. your skin has been changed to " .. skin_label) end } minetest.register_chatcommand ("skin" , param_skins ) minetest.register_chatcommand ("skins" , param_skins ) -- =================================================================== minetest.register_privilege ("skin", { description = "Can change skins for other players" , give_to_singleplayer=true }) -- =================================================================== local param_playerskins = { description = "list or change skins for a player" , params = " " , privs = { skin=true } , func = function (adname, params) local plname, label = string.match (params, "^([^ ]+) *(.*)") if not plname then plname = params end if ocutil.str_empty (plname) then plname = "" end if ocutil.str_empty (label ) then label = "" end plname = plname:gsub (" +$", "") label = label:gsub (" +$", "") if ocutil.str_empty (plname) then core.chat_send_player (adname, "Usage: /pskin player_name skin_label") return true end if not core.get_auth_handler().get_auth (plname) then core.chat_send_player (adname, "Error: No such player: " .. plname) return true end local print_prompt = true local skin_label = label local trbase if ocutil.str_empty (skin_label) then skin_label = "" end skin_label = skin_label:gsub ("%.png%", "") if ocutil.str_empty (skin_label) then trbase = get_stored_trbase (plname) if ocutil.str_nonempty (trbase) then cslog ("player " .. plname .. " has trbase " .. trbase) local old_label = trbase_to_label [trbase] if ocutil.str_empty (old_label) then old_label = trbase end if ocutil.str_nonempty (old_label) then print_prompt = false minetest.chat_send_player (adname, "Presently, their skin is " .. old_label .. ". " .. prompt) end end if print_prompt then minetest.chat_send_player (adname, prompt) end local buf = "" local npline = 13 local noline = 0 for _, skin_label in pairs (sorted_labels) do buf = buf .. " " .. skin_label noline = noline + 1 if noline >= npline then minetest.chat_send_player (adname, buf) buf = "" noline = 0 end end if noline > 0 then minetest.chat_send_player (adname, buf) end return end trbase = label_to_trbase [skin_label] if trbase == nil then local xp = skin_label:gsub ("%.png$", "") .. ".png" if coderskins.png_missing (xp) then xp = "skin_" .. xp if coderskins.png_missing (xp) then xp = xp:gsub ("^skin_", "player_") if coderskins.png_missing (xp) then minetest.chat_send_player (adname, "Not a valid skin label") return end end end trbase = xp:gsub ("%.png$", "") end if coderskins.png_missing (trbase .. ".png") then minetest.chat_send_player (adname, "Sorry, PNG file is missing") return end local oname = coderskins.overlay_name [plname] set_stored_trbase (plname, trbase) coderskins.change_skin (plname, oname) minetest.chat_send_player (adname, "O.K. their skin has been changed to " .. skin_label) end } minetest.register_chatcommand ("playerskin" , param_playerskins ) minetest.register_chatcommand ("pskin" , param_playerskins ) -- =================================================================== minetest.register_on_joinplayer (function (player) local plname = player:get_player_name() local skinpng = "player_" .. plname .. ".png" local final = nil -- =================================================================== coderskins.overlay_name [plname] = nil -- =================================================================== if coderskins.png_exists (skinpng) then cslog (plname .. ": " .. skinpng .. " exists") final = skinpng else cslog (plname .. ": " .. skinpng .. " doesn't exist") end -- =================================================================== if final == nil then skinpng = get_stored_skinpng (plname) if ocutil.str_empty (skinpng) then cslog (plname .. " doesn't have a recorded skin") else cslog (plname .. " has a recorded skin: " .. skinpng) if coderskins.png_exists (skinpng) then cslog (plname .. ": " .. skinpng .. " exists") final = skinpng else cslog (plname .. ": " .. skinpng .. " doesn't exist") end end end -- =================================================================== if final == nil then local rslist = { "Actual_Cat" , "Apple_Tree" , "Battery" , "Bear" , "Cola" , "Fox" , "Heather_Elf" , "Lego_Person" , "Lego_Spaceman" , "Marley_Xia" , "Puppy" , "Rabbit_Coco" , "Squirrel" , "Steampunk" , "Superpollo" , "Tiger" , } cslog ("Setting initial skin for " .. plname) local ii = math.random (#rslist) local charname = rslist [ii] skinpng = "skin_" .. charname .. ".png" cslog (plname .. ": Selected " .. skinpng .. " at random") if coderskins.png_exists (skinpng) then cslog (plname .. ": " .. skinpng .. " exists") final = skinpng else cslog (plname .. ": " .. skinpng .. " doesn't exist") end end -- =================================================================== if final == nil then skinpng = coderskins.fallback cslog (plname .. ": Falling back to " .. skinpng) if coderskins.png_exists (skinpng) then cslog (skinpng .. " exists") final = skinpng else ocutil.panic (skinpng .. " doesn't exist") end end -- =================================================================== cslog ("Final skin for " .. plname .. " is " .. final) set_stored_trbase (plname, final) coderskins.change_skin (plname) end) -- =================================================================== -- "skin" here is a base skin-file name that omits pathname component, -- "_preview*" part, and ".png". local list_pfront = {} local get_pfront = function (skin) local pfront = list_pfront [skin] if pfront ~= nil then return pfront end pfront = skin .. "_preview.png" local fp = io.open (coderskins.textdir .. pfront) if fp == nil then pfront = "coderskins_not_avail.png" else fp:close() end list_pfront [skin] = pfront return pfront end -- =================================================================== -- "skin" here is a base skin-file name that omits pathname component, -- "_preview*" part, and ".png". local list_pback = {} local get_pback = function (skin) local pback = list_pback [skin] if pback ~= nil then return pback end pback = skin .. "_preview_back.png" local fp = io.open (coderskins.textdir .. pback) if fp == nil then pback = "coderskins_not_avail.png" else fp:close() end list_pback [skin] = pback return pback end -- =================================================================== -- Display current skin. local param_main_page = { get_formspec = function (player) local plname = player:get_player_name() local skinpng = get_stored_skinpng (plname) local noext = skinpng:gsub ("%.png", "") local pfront = get_pfront (noext) local pback = get_pback (noext) local desc1 = coderskins.desc1 [noext] local desc2 = coderskins.desc2 [noext] if ocutil.str_empty (desc1) then local filedesc filedesc = noext:gsub ("sdskin_" , "") filedesc = noext:gsub ("skin_" , "") filedesc = filedesc:gsub ("sscharacter_" , "SS #" ) filedesc = filedesc:gsub ("uskins_" , "uskins #" ) filedesc = filedesc:gsub ("_" , " " ) desc1 = filedesc end if ocutil.str_empty (desc2) then desc2 = "" end -- Zero -based page number local zbpage = coderskins.pages [plname] if zbpage == nil then zbpage = 0 end local formspec = "background[0.06,0.99;7.92,7.52;ui_misc_form.png]" .. "image[0,.75;1,2;" .. pfront .. "]" .. "image[1,.75;1,2;" .. pback .. "]" .. "label[2.4,.75;" .. desc1 .. "]" .. "label[2.4,1.25;" .. desc2 .. "]" .. "label[6,.5;Raw texture:]" .. "image[6,1;2,1;" .. skinpng .. "]" .. "button[.75,3;6.5,.5;coderskins_page_" .. zbpage .. ";Change]" return { formspec = formspec } end , } unified_inventory.register_page ("coderskins", param_main_page) -- =================================================================== local param_main_button = { type = "image" , image = "coderskins_button.png" , tooltip = "CoderSkins" , } unified_inventory.register_button ("coderskins", param_main_button) -- =================================================================== -- Create the skin-picker pages. local nsskins = #coderskins.list local nspages = math.floor (nsskins / 16) if nsskins % 16 ~= 0 then nspages = nspages + 1 end if nspages > 99 then nspages = 99 end cslog ("number of skins is " .. nsskins) cslog ("number of skin pages is " .. nspages) for x = 0, nspages do unified_inventory.register_page ("coderskins_page_" .. x, { get_formspec = function (player) local plname = player:get_player_name() -- Zero -based page number local zbpage = coderskins.pages [plname] if zbpage == nil then zbpage = 0 end -- One -based page number local obpage = zbpage + 1 local formspec = "background[0.06,0.99;7.92,7.52;ui_misc_form.png]" local index = 0 local skip = 0 -- Skip coderskins, used for pages -- Skin thumbnails for i, x_skin in ipairs (coderskins.list) do local skin = x_skin:gsub (".png", "") if skip < zbpage*16 then skip = skip+1 else if index < 16 then local pfront = get_pfront (skin) formspec = formspec .. "image_button[" .. (index % 8) .. "," .. ((math.floor (index / 8))*2) .. ";1,2;" .. pfront formspec = formspec .. ";coderskins_set_" .. i .. ";]" end index = index+1 end end -- "Prev" page button if zbpage > 0 then formspec = formspec .. "button[0,4;1,.5;coderskins_page_" .. (zbpage-1) .. ";<<]" else formspec = formspec .. "button[0,4;1,.5;coderskins_page_" .. zbpage .. ";<<]" end if false then formspec = formspec .. "button[.75,4;6.5,.5;coderskins_page_" .. zbpage .. ";Page " .. obpage .. "/" .. nspages .. "]" else local pba, pbx, pbz, pbq if obpage > 1 then pbq = "button[1,4;1,.5;coderskins_page_0;01]" formspec = formspec .. pbq elseif nspages > 3 then pbq = "button[1,4;1,.5;coderskins_page_1;02]" formspec = formspec .. pbq end if nspages >= 6 then pba = math.floor ((nspages / 3) + 0.5) if pba < 1 then pba = 1 end pbx = tostring (pba) if pba < 10 then pbx = "0" .. pbx end pbz = tostring (pba-1) .. ";" .. pbx .. "]" pbq = "button[2,4;1,.5;coderskins_page_" .. pbz formspec = formspec .. pbq end pbq = "button[3,4;2,.5;coderskins_page_" .. zbpage .. ";Page " .. obpage .. "/" .. nspages .. "]" formspec = formspec .. pbq if nspages >= 6 then pba = math.floor ((nspages * (2/3)) + 0.5) if pba < 1 then pba = 1 end pbx = tostring (pba) if pba < 10 then pbx = "0" .. pbx end pbz = tostring (pba-1) .. ";" .. pbx .. "]" pbq = "button[5,4;1,.5;coderskins_page_" .. pbz formspec = formspec .. pbq end if nspages > 3 then if obpage == nspages then pba = nspages - 1 if pba < 1 then pba = 1 end else pba = nspages end if pba ~= obpage then pbx = tostring (pba) if pba < 10 then pbx = "0" .. pbx end pbz = tostring (pba-1) .. ";" .. pbx .. "]" pbq = "button[6,4;1,.5;coderskins_page_" .. pbz formspec = formspec .. pbq end end end -- "Next" page button if index > 16 then formspec = formspec .. "button[7,4;1,.5;coderskins_page_" .. (zbpage+1) .. ";>>]" else formspec = formspec .. "button[7,4;1,.5;coderskins_page_" .. zbpage .. ";>>]" end return { formspec = formspec } end , }) end -- =================================================================== minetest.register_on_player_receive_fields (function (player, formname, fields) local plname = player:get_player_name() if fields.coderskins then unified_inventory.set_inventory_formspec (player, "craft") end local setpage = false for field, _ in pairs (fields) do if string.sub (field, 0, string.len ("coderskins_page_")) == "coderskins_page_" then local zbpage = tonumber (string.sub (field, string.len ("coderskins_page_") + 1)) coderskins.pages [plname] = zbpage unified_inventory.set_inventory_formspec (player, "coderskins_page_" .. zbpage) setpage = true end end for field, _ in pairs (fields) do if string.sub (field, 0, string.len ("coderskins_set_")) == "coderskins_set_" then local trbase = coderskins.list [tonumber (string.sub (field, string.len ("coderskins_set_") + 1))] set_stored_trbase (plname, trbase) coderskins.change_skin (plname) if setpage == false then unified_inventory.set_inventory_formspec (player, "coderskins") end end end end) -- =================================================================== -- End of file.