Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add migration code to player skins using '.' delimiters #105

Merged
merged 4 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@
local storage = minetest.get_mod_storage()

function skins.get_player_skin(player)
local player_name = player:get_player_name()
local meta = player:get_meta()
if meta:get("skinsdb:skin_key") then
-- Move player data prior July 2018 to mod storage
storage:set_string(player:get_player_name(), meta:get_string("skinsdb:skin_key"))
storage:set_string(player_name, meta:get_string("skinsdb:skin_key"))
meta:set_string("skinsdb:skin_key", "")
end
local skin = storage:get_string(player:get_player_name())
return skins.get(skin) or skins.get(skins.default)

local skin_name = storage:get_string(player_name)
local skin = skins.get(skin_name)
if #skin_name > 0 and not skin then
-- Migration step to convert `_`-delimited skins to `.` (if possible)
skin = skins.__fuzzy_match_skin_name(player_name, skin_name, true)
if skin then
storage:set_string(player_name, skin:get_key())
else
storage:set_string(player_name, "")
end
end
return skin or skins.get(skins.default)
end

-- Assign skin to player
Expand Down
3 changes: 3 additions & 0 deletions init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,6 @@ minetest.register_allow_player_inventory_action(function(player, action, inv, da
return 0
end
end)

--dofile(skins.modpath.."/unittest.lua")

59 changes: 49 additions & 10 deletions skinlist.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ local dbgprint = false and print or function() end

--- @param path Path to the "textures" directory, without tailing slash.
--- @param filename Current file name, such as "player.groot.17.png".
local function process_skin_texture(path, filename)
--- @return On error: false, error message. On success: true, skin key
function skins.register_skin(path, filename)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be added to the API documentation in a later PR.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The migration seems to work, I could not find a case where it would not. OK
  2. register_skin is now public and correctly adds the skin from a different mod (meta not tested) OK
  3. Private skins show up in the list OK

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume list point "3." is OK too? 🙃

In either case - thank you for testing. If there's no further issues found, I'll go ahead merging this PR in about 24 hours.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely certain about point 3 - that's the hand key ambiguity. I looked at the key again and found it still ambiguous, but I thought that's because this issue wasn't marked addressed in this PR.

My goal is to externalize skins from this mod, so we can go "full upstream", while maintaining our skins in a worldmods folder #31 and having a way to retrofit skins into a running server (currently blocked by the problem that the hand is a new block, which cannot be registered during runtime)

-- See "textures/readme.txt" for allowed formats

local prefix, sep, identifier, extension = filename:match("^(%a+)([_.])([%w_.]+)%.(%a+)$")
Expand All @@ -16,17 +17,21 @@ local function process_skin_texture(path, filename)

-- Filter out files that do not match the allowed patterns
if not extension or extension:lower() ~= "png" then
return -- Not a skin texture
return false, "invalid skin name"
end
if prefix ~= "player" and prefix ~= "character" then
return -- Unknown type
return false, "unknown type"
end

local preview_suffix = sep .. "preview"
if identifier:sub(-#preview_suffix) == preview_suffix then
-- skip preview textures
-- This is added by the main skin texture (if exists)
return
-- 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
end

dbgprint("Found skin", prefix, identifier, extension)
Expand Down Expand Up @@ -58,12 +63,16 @@ local function process_skin_texture(path, filename)
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)
if sep ~= "_" then
skin_obj._legacy_name = filename_noext:gsub("[._]+", "_")
end

if playername then
skin_obj:set_meta("assignment", "player:"..playername)
skin_obj:set_meta("playername", playername)
end

do
if path then
-- Get type of skin based on dimensions
local file = io.open(path .. "/" .. filename, "r")
local skin_format = skins.get_skin_format(file)
Expand All @@ -74,7 +83,7 @@ local function process_skin_texture(path, filename)
skin_obj:set_hand_from_texture()
skin_obj:set_meta("name", identifier)

do
if path then
-- Optional skin information
local file = io.open(path .. "/../meta/" .. filename_noext .. ".txt", "r")
if file then
Expand All @@ -86,7 +95,7 @@ local function process_skin_texture(path, filename)
end
end

do
if path then
-- Optional preview texture
local preview_name = filename_noext .. sep .. "preview.png"
local fh = io.open(path .. "/" .. preview_name)
Expand All @@ -95,6 +104,36 @@ local function process_skin_texture(path, filename)
skin_obj:set_preview(preview_name)
end
end

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
end

do
Expand All @@ -103,7 +142,7 @@ do
local skins_dir_list = minetest.get_dir_list(skins_path)

for _, fn in pairs(skins_dir_list) do
process_skin_texture(skins_path, fn)
skins.register_skin(skins_path, fn)
end
end

Expand Down
2 changes: 1 addition & 1 deletion textures/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The character `_` is accepted in player names, thus it is not recommended to
use such file names. For compatibility reasons, they are still recognized.

character_[number or name].png
player_[nick]_png
player_[nick].png
player_[nick]_[number or name].png

... and corresponding previews that end in `_preview.png`.
50 changes: 50 additions & 0 deletions unittest.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
local function get_skin(skin_name)
local skin = skins.get(skin_name)
or skins.__fuzzy_match_skin_name("(unittest)", skin_name, true)
return skin and skin:get_key() or nil
end

local function run_unittest()
local PATH = ":UNITTEST:"

-- -----
-- `.`: Simple register + retrieve operations
skins.register_skin(PATH, "player.DotSep.png")
skins.register_skin(PATH, "player._DotSep_666_.1.png")

assert(get_skin("player.DotSep"))
assert(get_skin("player._DotSep_666_.1"))
assert(get_skin("player.DotSep.1") == nil)

-- -----
-- Ambiguous skin names (filenames without extension). Register + retrieve
skins.new("player_AmbSki")
skins.new("player_AmbSki_1")
skins.new("player_AmbSki_666_1")

assert(get_skin("player_AmbSki"))
assert(get_skin("player_AmbSki_") == nil)
assert(get_skin("player_AmbSki_1"))
assert(get_skin("player_AmbSki_666_1"))
-- There are no `__` patterns as they were silently removed by string.split


-- -----
-- Mod Storage backwards compatibility
-- Match the old `_` notation to `.`-separated skins
skins.register_skin(PATH, "player.ComPat42.png")
skins.register_skin(PATH, "player.ComPat42.5.png")
skins.register_skin(PATH, "player._Com_Pat_42.png")
skins.register_skin(PATH, "player._Com_Pat_42.1.png")

assert(get_skin("player_ComPat42") == "player.ComPat42")
assert(get_skin("player_ComPat42_5") == "player.ComPat42.5")
assert(get_skin("player_Com_Pat_42") == "player._Com_Pat_42")
assert(get_skin("player_Com_Pat_42_1") == "player._Com_Pat_42.1")


error("Unittest passed! Please disable them now.")
end

run_unittest()