Module:LoRUtility: Difference between revisions

From League of Legends Wiki, LoL Wiki
Jump to navigation Jump to search
Content added Content deleted
(Add function for LoR artwork content)
mNo edit summary
Line 519: Line 519:
for _, artwork in pairs(lorData[cardcode].artworks) do
for _, artwork in pairs(lorData[cardcode].artworks) do
artworkContent = 'Another Artwork'
artworkContent = 'Another Artwork'
local extension = 'png'


if artwork == 'hd' then
if artwork == 'hd' then
artworkContent = 'HD Artwork'
artworkContent = 'HD Artwork'
extension = 'jpg'
elseif artwork == 'alt-hd' then
elseif artwork == 'alt-hd' then
artworkContent = 'HD Alternate Artwork'
artworkContent = 'HD Alternate Artwork'
extension = 'jpg'
end
end


artworkLink = "<li style='display: inline-block;'>[[File:" .. cardcode .. "-" .. artwork .. "-full.png|thumb|135px|" .. artworkContent .. "|left]]</li>"
artworkLink = "<li style='display: inline-block;'>[[File:" .. cardcode .. "-" .. artwork .. "." .. extension .. "|thumb|135px|" .. artworkContent .. "|left]]</li>"


if artworkText ~= '' then
if artworkText ~= '' then

Revision as of 11:29, 11 December 2020

Documentation for this module may be created at Module:LoRUtility/doc

-- <pre>
-- Inherit from https://github.com/SwitchbladeBot/runeterra

local lib               = require('Module:Feature')
local MAX_KNOWN_VERSION = 2
local p                 = {}
local VarInt            = {}
local BitOperator       = {}
local Utility           = {}

local REGIONS = {
    [1]  = {code = "DE", name = "Demacia"},
    [2]  = {code = "FR", name = "Freljord"},
    [3]  = {code = "IO", name = "Ionia"},
    [4]  = {code = "NX", name = "Noxus"},
    [5]  = {code = "PZ", name = "Piltover and Zaun"},
    [6]  = {code = "SI", name = "Shadow Isles"},
    [7]  = {code = "BW", name = "Bilgewater"},
    [10] = {code = "MT", name = "Targon"}
}

function base32_decode(str)
    local base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
    
    local binary =
        str:gsub(
        ".",
        function(char)
            if char == "=" then
                return ""
            end
            local pos = string.find(base32Alphabet, char)
            pos = pos - 1
            return string.format("%05u", Utility.dec2bin(pos))
        end
    )
    
    local bytes = Utility.str_split(binary, 8)

    local result = {}
    for _, byte in pairs(bytes) do
        table.insert(result, tonumber(byte, 2))
    end

    return result
end

function p.deckDataFromCode(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local lorData   = mw.loadData("Module:LoRData/data")
    local bytes     = base32_decode(args['code'])
    local firstByte = bytes[1]
    local deckList  = {}

    table.remove(bytes, 1)
    local version = BitOperator.AND(tonumber(firstByte), 0xF)

    if version > MAX_KNOWN_VERSION then
        return "The provided code requires a higher version of this library; please update."
    end
    
    local totalShards = 0
    local regions = {}
    local followerCount = 0
    local spellCount = 0
    local landmarkCount = 0
    local rarities = {}
    local champions = {}
    local shardValues = {
        ["None"] = 0,
        ["Common"] = 100,
        ["Rare"] = 300,
        ["Epic"] = 1200,
        ["Champion"] = 3000,
    }
    local topChampion = nil
    local topFollower = nil
    
    function handleCardData(cardData, count, cardCode, regionName)
        local cardType = cardData.type
        if cardType == "Unit" then
            if cardData.supertype ~= nil and cardData.supertype == "Champion" then
                cardType = "Champion"
            else
                cardType = "Follower"
            end
        end
        
        if args['detail'] then
            totalShards = totalShards + shardValues[cardData.rarity] * count
            if cardData.rarity ~= 'None' then
                if cardData.rarity == 'Common' then 
                    rarities[1] = (rarities[1] or 0) + count
                elseif cardData.rarity == 'Rare' then 
                    rarities[2] = (rarities[2] or 0) + count
                elseif cardData.rarity == 'Epic' then 
                    rarities[3] = (rarities[3] or 0) + count
                else
                    rarities[4] = (rarities[4] or 0) + count
                end
            end
            
            if cardType == "Champion" or cardType =='Follower' then
                if cardType == "Champion" then
                    table.insert(champions, {
                        code = cardCode,
                        count = count
                    })
                    if topChampion == nil or cardData.cost > topChampion.cost then
                        topChampion = {
                            cost = cardData.cost,
                            code = cardCode
                        }
                    end
                else
                    if topFollower == nil or cardData.cost > topFollower.cost then
                        topFollower = {
                            cost = cardData.cost,
                            code = cardCode
                        }
                    end
                    followerCount = followerCount + count
                end
            elseif cardType == "Spell" then
                spellCount = spellCount + count
            elseif cardType == "Landmark" then
            	landmarkCount = landmarkCount + count
            end
            
            regions[regionName] = (regions[regionName] or 0) + count
        else
            table.insert(
                deckList,
                {
                    code = cardCode,
                    name = cardData.name,
                    count = count,
                    region = regionName,
                    type = cardType
                }
            )
        end
    end
    
    for i = 3, 0, -1 do
        local numGroupOfs = tonumber(VarInt.Pop(bytes)) or 0
        for j = 1, numGroupOfs, 1 do
            local numOfsInThisGroup = tonumber(VarInt.Pop(bytes)) or 0
            local set = VarInt.Pop(bytes)
            local region = VarInt.Pop(bytes)
            
            for k = 1, numOfsInThisGroup, 1 do
                local card = VarInt.Pop(bytes)
                if card ~= nil then
                    local setString = Utility.padLeft(tostring(set), 2, "0")
                    local regionData = REGIONS[region + 1]
                    local cardString = Utility.padLeft(tostring(card), 3, "0")
    
                    local cardCode = setString .. regionData.code .. cardString
    
                    if (not lorData[cardCode]) then
                        return 'Card Code "' .. cardCode .. '" does not exist'
                    end
                    handleCardData(lorData[cardCode], i, cardCode, regionData.name)
                end
            end
        end
    end

    while #bytes > 0 do
        local fourPlusCount = VarInt.Pop(bytes)
        local fourPlusSet = VarInt.Pop(bytes)
        local fourPlusFaction = VarInt.Pop(bytes)
        local fourPlusNumber = VarInt.Pop(bytes)

        if fourPlusFaction == nil then
            return 'Invalid Deck Code'
        end
        
        local fourPlusSetString = Utility.padLeft(tostring(fourPlusSet), 2, "0")
        local fourPlusregionData = REGIONS[fourPlusFaction + 1]
        local fourPlusNumberString = Utility.padLeft(tostring(fourPlusNumber), 3, "0")

        local cardCode = fourPlusSetString .. fourPlusregionData.code .. fourPlusNumberString

        if (not lorData[cardCode]) then
            return 'Card Code "' .. cardCode .. '" is not exist'
        end
        
        handleCardData(lorData[cardCode], fourPlusNumber, cardCode, regionData.name)
    end
    
    if args['detail'] then
        return {
            shards = totalShards,
            topUnitCode = (topChampion or topFollower).code,
            regions = regions,
            champions = champions,
            followerCount = followerCount,
            spellCount = spellCount,
            landmarkCount = landmarkCount,
            rarities = rarities
        }
    else 
        return deckList
    end
end

function p.headerDeck(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    args['name'] = args['name'] or 'Untitled Deck'
    
    if args['code'] == nil then
        return 'Empty Code'
    end
    
    local result = {}
    
    local deckDetail = p.deckDataFromCode{code = args['code'], detail = true}
    if type(deckDetail) ~= 'table' then
        return deckDetail
    end
    
    local totalShards   = deckDetail['shards'] or nil
    local topUnitCode   = deckDetail['topUnitCode'] or nil
    local regions       = deckDetail['regions'] or nil
    local champions     = deckDetail['champions'] or nil
    local followerCount = deckDetail['followerCount'] or nil
    local spellCount    = deckDetail['spellCount'] or nil
    local landmarkCount = deckDetail['landmarkCount'] or nil
    local rarities      = deckDetail['rarities'] or nil
    
    local template = mw.html.create('div')
        :css('position', 'relative')
        :css('font-family', 'BeaufortLoL')
        :tag('div')
            :css('opacity', '0.5')
            :wikitext('[[File:' .. topUnitCode .. '-full.png|700px|link=]]')
            :done()
            
    local deckHeader = mw.html.create('div')
            :addClass('deck-header')
            :css('position', 'absolute')
            :css('top', '10px')
            :css('left', '10px')
            :tag('span')
                :css('font-weight', 'bold')
                :css('color', '#FFE3B0')
                :css('font-size', '30px')
                :css('margin-left', '10px')
                :css('margin-right', '10px')
                :wikitext(args['name'])
                :done()
    for regionName, regionCount in pairs(regions) do
        deckHeader
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:' .. regionName .. ' LoR Region.png|60px|link=' .. regionName .. ' (Legends of Runeterra)]]')
                :done()
    end 
    deckHeader
            :tag('span')
                :css('display', 'inline-block')
                :css('position', 'relative')
                :css('color', '#FFE3B0')
                :css('font-size', '16px')
                :wikitext('[[File:Shard icon.png|40px|link=Shard (Legends of Runeterra)]]' .. Utility.comma_value(totalShards))
            :done()
    deckHeader:done()
    
    local deckInfo = mw.html.create('div')
        :addClass('deck-info')
        :css('position', 'absolute')
        :css('top', '65px')
        :css('left', '20px')
        :css('font-weight', 'bold')
        :css('color', '#FFE3B0')
            
    if args['description'] then
        deckInfo
            :tag('span')
                :css('display', 'inline-block')
                :css('max-width', '660px')
                :css('font-size', '16px')
                :wikitext(args['description'])
            :done()
    end
        deckInfo:done()
    
    template
        :node(deckHeader)
        :node(deckInfo)
        :tag('div')
            :addClass('deck-info')
            :css('position', 'absolute')
            :css('top', '170px')
            :css('left', '20px')
            :css('font-size', '20px')
            :css('color', '#FFE3B0')
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Follower card.svg|25px|link=Follower (Legends of Runeterra)]]' .. followerCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Spell card.svg|25px|link=Spell (Legends of Runeterra)]]' .. spellCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Keyword Landmark.png|25px|link=Landmark (Legends of Runeterra)]]' .. landmarkCount .. '&nbsp;')
            :done()
            :tag('br')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Common rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[1] or 0) .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Rare rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[2] or 0) .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Epic rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[3] or 0) .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Champion rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[4] or 0) .. '&nbsp;')
            :done()
        :done()
    
    if #champions then
        local deckChampions = mw.html.create('div')
            :addClass('deck-champions')
            :css('position', 'absolute')
            :css('bottom', '20px')
            :css('left', '20px')
            :css('font-size', '20px')
            :css('color', '#FFE3B0')
                :tag('span')
                    :addClass('inline-image')
                    :wikitext('[[File:Champion card.svg|25px|link=Champion (Legends of Runeterra)]]')
                :done()
        for _, deckData in pairs(champions) do
            deckChampions
                :tag('span')
                    :addClass('inline-image')
                    :wikitext('[[File:' .. deckData.code .. '.png|60px|link='.. deckData.code .. ' (Legends of Runeterra)]]')
                :done()
                :tag('span')
                    :addClass('inline-image')
                    :wikitext('x' .. deckData.count .. '&nbsp;')
                :done()
        end
        deckChampions:
            done()
        template
            :node(deckChampions)
    end
    
    template:allDone()
    
    return tostring(template)
end

function p.deckDecoder(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    if args['code'] == nil then
        return 'Empty Code'
    end
    
    local groupBy = args['groupBy'] or 'none'
    local groupByValues = {'region', 'type', 'none'}
    if Utility.table_contains(groupByValues, groupBy) == false then
        return 'Only support Group By "region", "type" or none'
    end
    
    local result = {}
    
    local deckList = p.deckDataFromCode{code = args['code']}
    if type(deckList) ~= 'table' then
        return deckList
    end
    
    for _, deckData in pairs(deckList) do
        local groupByKey = ''
        if groupBy == 'region' then
            groupByKey = deckData.region
        elseif groupBy == 'type' then
            groupByKey = cardType .. 's'
        else
            groupByKey = 'List'
        end
        
        if result[groupByKey] == nil then
            result[groupByKey] = {}
        end
        
        table.insert(
            result[groupByKey],
            {
                code  = deckData.code,
                count = deckData.count,
                name  = deckData.name,
                type  = deckData.type
            }
        )
    end

    local sortedKeys = {}
    for k in pairs(result) do
        table.insert(sortedKeys, k)
    end
    table.sort(sortedKeys)
    
    local template = ""
    template = template .. p.headerDeck{code = args['code'], name = args['name'], description = args['description']}
    
    local newline = "\n"
    template = template .. "<tabber>" .. newline
    template = template .. "Hide=" .. newline .. '|-|' .. newline
    for i, groupByKey in ipairs(sortedKeys) do
        if i ~= 1 then
            template = template .. "|-|" .. newline
        end
        template = template .. groupByKey .. "=" .. newline .. "'''Code:''' " .. args['code'] .. newline

        local regionData = result[groupByKey]
        table.sort(
            regionData,
            function(a, b)
                if (a.type == b.type) then
                    return a.name < b.name
                end
                return a.type < b.type
            end
        )

        local type = ""
        for _, cardData in ipairs(regionData) do
            if cardData.type ~= type then
                if (type ~= "") then
                    template = template .. "}}" .. newline
                end
                if groupBy == 'region' or groupBy == 'none' then
                    template = template .. ";" .. cardData.type .. "s" .. newline
                end
                template = template .. "{{column|3|" .. newline

                type = cardData.type
            end

            local cardCodeTemplate = "* {{LoR|" .. cardData.name
            if cardData.type ~= "Champion" then
                cardCodeTemplate = cardCodeTemplate .. "|code=" .. cardData.code
            end
            cardCodeTemplate = cardCodeTemplate .. "}}"
            if cardData.count > 1 then
                cardCodeTemplate = cardCodeTemplate .. " (x" .. cardData.count .. ")"
            end

            template = template .. cardCodeTemplate .. newline
        end

        template = template .. "}}" .. newline
    end
    template = template .. "</tabber>"

    return frame:preprocess(template)
end

function p.subtypeContent(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local cardcode = args['code'] or args[1] or nil
    
    if not cardcode then
    	return ''
    end
    
    local lorData   = mw.loadData('Module:LoRData/data')
    
    subtypetext = ''
    for _, subtype in pairs(lorData[cardcode].subtype) do
    	subtypelink = '[[' .. subtype .. ' (Legends of Runeterra)|' .. subtype .. ']]'
    	if subtypetext ~= '' then
    		subtypetext = subtypetext .. ", " .. subtypelink
    	else
    		subtypetext = subtypetext .. subtypelink
    	end
    	
    	subtypetext = subtypetext .. "[[" .. "Category:LoR " .. subtype .. "]]"
    end
    
    return frame:preprocess(subtypetext)
end

function p.artworkContent(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local cardcode = args['code'] or args[1] or nil
    
    if not cardcode then
    	return ''
    end
    
    local lorData   = mw.loadData('Module:LoRData/data')
    
    artworkText = "<ul style='margin-left:0'>"
    artworkText = artworkText .. "<li style='display: inline-block;'>[[File:" .. cardcode .. "-full.png|thumb|135px|Main Artwork|left]]</li>"

    if lorData[cardcode].alternate then
        artworkText = artworkText .. "<li style='display: inline-block;'>[[File:" .. cardcode .. "-alt-full.png|thumb|135px|Alternate Artwork|left]]"
    end

    for _, artwork in pairs(lorData[cardcode].artworks) do
        artworkContent = 'Another Artwork'
        
        local extension = 'png'

        if artwork == 'hd' then
            artworkContent = 'HD Artwork'
            extension = 'jpg'
        elseif artwork == 'alt-hd' then
            artworkContent = 'HD Alternate Artwork'
            extension = 'jpg'
        end

        artworkLink = "<li style='display: inline-block;'>[[File:" .. cardcode .. "-" .. artwork .. "." .. extension .. "|thumb|135px|" .. artworkContent .. "|left]]</li>"

        if artworkText ~= '' then
            artworkText = artworkText .. " " .. artworkLink
        else
            artworkText = artworkText .. artworkLink
        end
    end

    artworkText = artworkText .. "</ul>"
    
    return frame:preprocess(artworkText)
end

-- VarInit
VarInt.AllButMSB = 0x7f
VarInt.JustMSB = 0x80

function VarInt.Pop(bytes)
    local result = 0
    local currentShift = 0
    local bytesPopped = 0

    for i = 1, #bytes do
        bytesPopped = bytesPopped + 1
        local current = BitOperator.AND(bytes[i], VarInt.AllButMSB)
        result = BitOperator.OR(result, BitOperator.RIGHT_SHIFT(current, currentShift))

        if BitOperator.AND(bytes[i], VarInt.JustMSB) ~= VarInt.JustMSB then
            Utility.table_splice(bytes, 1, bytesPopped)
            return result
        end

        currentShift = currentShift + 7
    end

    -- error('Invalid Deck Code: Byte array did not contain valid varints.')
    return nil
end

-- BitOperator
function BitOperator.OR(m, n)
    local tbl_m = Utility.to_bits(m)
    local tbl_n = Utility.to_bits(n)
    Utility.expand(tbl_m, tbl_n)

    local tbl = {}
    local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
    for i = 1, rslt do
        if (tbl_m[i] == 0 and tbl_n[i] == 0) then
            tbl[i] = 0
        else
            tbl[i] = 1
        end
    end

    return Utility.table2number(tbl)
end

function BitOperator.AND(m, n)
    local tbl_m = Utility.to_bits(m)
    local tbl_n = Utility.to_bits(n)
    Utility.expand(tbl_m, tbl_n)

    local tbl = {}
    local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
    for i = 1, rslt do
        if (tbl_m[i] == 0 or tbl_n[i] == 0) then
            tbl[i] = 0
        else
            tbl[i] = 1
        end
    end

    return Utility.table2number(tbl)
end

function BitOperator.RIGHT_SHIFT(n, bits)
    Utility.check_int(n)

    local high_bit = 0
    if (n < 0) then
        -- negative
        n = BitOperator.NOT(math.abs(n)) + 1
        high_bit = 2147483648 -- 0x80000000
    end

    for i = 1, bits do
        n = n / 2
        n = BitOperator.OR(math.floor(n), high_bit)
    end
    return math.floor(n)
end

function BitOperator.NOT(n)
    local tbl = Utility.to_bits(n)
    local size = math.max(table.getn(tbl), 32)
    for i = 1, size do
        if (tbl[i] == 1) then
            tbl[i] = 0
        else
            tbl[i] = 1
        end
    end
    return Utility.table2number(tbl)
end

function BitOperator.LEFT_SHIFT(n, bits)
    Utility.check_int(n)

    if (n < 0) then
        -- negative
        n = BitOperator.NOT(math.abs(n)) + 1
    end

    for i = 1, bits do
        n = n * 2
    end
    return BitOperator.AND(n, 4294967295) -- 0xFFFFFFFF
end

-- Utility
function Utility.str_split(str, size)
    local result = {}
    for i = 1, #str, size do
        table.insert(result, str:sub(i, i + size - 1))
    end
    return result
end

function Utility.dec2bin(num)
    local result = ""
    repeat
        local halved = num / 2
        local int, frac = math.modf(halved)
        num = int
        result = math.ceil(frac) .. result
    until num == 0
    return result
end

function Utility.padRight(str, length, char)
    while #str % length ~= 0 do
        str = str .. char
    end
    return str
end

function Utility.padLeft(str, length, char)
    while #str % length ~= 0 do
        str = char .. str
    end
    return str
end

function Utility.table2number(tbl)
    local n = table.getn(tbl)

    local rslt = 0
    local power = 1
    for i = 1, n do
        rslt = rslt + tbl[i] * power
        power = power * 2
    end

    return rslt
end

function Utility.to_bits(n)
    Utility.check_int(n)
    if (n < 0) then
        -- negative
        return Utility.to_bits(bit.bnot(math.abs(n)) + 1)
    end
    -- to bits table
    local tbl = {}
    local cnt = 1
    while (n > 0) do
        local last = math.mod(n, 2)
        if (last == 1) then
            tbl[cnt] = 1
        else
            tbl[cnt] = 0
        end
        n = (n - last) / 2
        cnt = cnt + 1
    end

    return tbl
end

function Utility.check_int(n)
    -- checking not float
    if (n - math.floor(n) > 0) then
        error("trying to use bitwise operation on non-integer!")
    end
end

function Utility.expand(tbl_m, tbl_n)
    local big = {}
    local small = {}
    if (table.getn(tbl_m) > table.getn(tbl_n)) then
        big = tbl_m
        small = tbl_n
    else
        big = tbl_n
        small = tbl_m
    end
    -- expand small
    for i = table.getn(small) + 1, table.getn(big) do
        small[i] = 0
    end
end

function Utility.table_splice(tab, idx, n, ...) --> removed
    local values = {...}
    local init_tab_size = #tab

    local removed = {}
    if n > 0 then
        for i = idx, (idx + n - 1) do
            table.insert(removed, tab[i])
            tab[i] = nil
        end
    end

    local tail = {}
    for i = (idx + n), init_tab_size do
        table.insert(tail, tab[i])
        tab[i] = nil
    end

    local i = idx
    for _, v in ipairs(values) do
        tab[i] = v
        i = i + 1
    end

    i = idx + #values
    for _, v in ipairs(tail) do
        tab[i] = v
        i = i + 1
    end

    return removed
end

function Utility.table_contains(table, val)
   for i = 1, #table do
      if table[i] == val then 
         return true
      end
   end
   return false
end

function Utility.comma_value(n) -- credit http://richard.warburton.it
	local left, num, right = string.match(n, '^([^%d]*%d)(%d*)(.-)$')
	return left .. (num:reverse():gsub('(%d%d%d)', '%1,'):reverse()) .. right
end

return p
-- </pre>
-- [[Category:Lua]]