Module:LoRUtility: Difference between revisions

From League of Legends Wiki, LoL Wiki
Jump to navigation Jump to search
Content added Content deleted
mNo edit summary
No edit summary
 
(197 intermediate revisions by 12 users not shown)
Line 1: Line 1:
-- <pre>
-- <pre>
-- Inherit from https://github.com/SwitchbladeBot/runeterra


local lib = require('Module:Feature')
local lib = require('Module:Feature')
local MAX_KNOWN_VERSION = 2
local MAX_KNOWN_VERSION = 5
local p = {}
local p = {}
local VarInt = {}
local VarInt = {}
Line 17: Line 16:
[6] = {code = "SI", name = "Shadow Isles"},
[6] = {code = "SI", name = "Shadow Isles"},
[7] = {code = "BW", name = "Bilgewater"},
[7] = {code = "BW", name = "Bilgewater"},
[10] = {code = "MT", name = "Targon"}
[8] = {code = "SH", name = "Shurima"},
[10] = {code = "MT", name = "Targon"},
[11] = {code = "BC", name = "Bandle City"},
[13] = {code = "RU", name = "Runeterra"}
}
}

--To test debug this line mw.executeFunction(p.test)
function p.test(frame)
output = p.adventureRegions{"RUIODE--"}
mw.log(output)
end


function base32_decode(str)
function base32_decode(str)
Line 47: Line 55:


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

table.remove(bytes, 1)
table.remove(bytes, 1)
local version = BitOperator.AND(tonumber(firstByte), 0xF)
local version = BitOperator.AND(tonumber(firstByte), 0xF)
Line 66: Line 74:
local spellCount = 0
local spellCount = 0
local landmarkCount = 0
local landmarkCount = 0
local equipmentCount = 0
local rarities = {}
local rarities = {}
local champions = {}
local champions = {}
Line 77: Line 86:
local topChampion = nil
local topChampion = nil
local topFollower = nil
local topFollower = nil
local runeterranChampions = {}
local allCards = {}
function handleCardData(cardData, count, cardCode, regionName)
function handleCardData(cardData, count, cardCode, regionName)
Line 83: Line 94:
if cardData.supertype ~= nil and cardData.supertype == "Champion" then
if cardData.supertype ~= nil and cardData.supertype == "Champion" then
cardType = "Champion"
cardType = "Champion"
if regionName == "Runeterra" then
table.insert(runeterranChampions,cardData.name)
end
else
else
cardType = "Follower"
cardType = "Follower"
Line 88: Line 102:
end
end
table.insert(allCards,{code=cardCode, count=count, regionName=regionName})
regions[regionName] = (regions[regionName] or 0) + count
if args['detail'] then
if args['detail'] then
totalShards = totalShards + shardValues[cardData.rarity] * count
totalShards = totalShards + shardValues[cardData.rarity] * count
Line 127: Line 143:
elseif cardType == "Landmark" then
elseif cardType == "Landmark" then
landmarkCount = landmarkCount + count
landmarkCount = landmarkCount + count
elseif cardType == "Equipment" then
equipmentCount = equipmentCount + count
end
end
regions[regionName] = (regions[regionName] or 0) + count
else
else
table.insert(
table.insert(
Line 144: Line 161:
end
end
for i = 3, 0, -1 do
for i = 3, 1, -1 do
local numGroupOfs = tonumber(VarInt.Pop(bytes)) or 0
local numGroupOfs = tonumber(VarInt.Pop(bytes)) or 0
for j = 1, numGroupOfs, 1 do
for j = 1, numGroupOfs, 1 do
Line 168: Line 185:
end
end
end
end

--For cards with over 3 copies
while true do
local count = tonumber(VarInt.Pop(bytes)) or nil
local set = VarInt.Pop(bytes)
local region = VarInt.Pop(bytes)
local card = VarInt.Pop(bytes)
if not count or not set or not region or not card then
break --end of stream
end
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], count, cardCode, regionData.name)
end
while #bytes > 0 do
while #bytes > 0 do
local fourPlusCount = VarInt.Pop(bytes)
local fourPlusCount = VarInt.Pop(bytes)
Line 186: Line 226:


if (not lorData[cardCode]) then
if (not lorData[cardCode]) then
return 'Card Code "' .. cardCode .. '" is not exist'
return 'Card Code "' .. cardCode .. '" does not exist'
end
end
handleCardData(lorData[cardCode], fourPlusNumber, cardCode, regionData.name)
handleCardData(lorData[cardCode], fourPlusCount, cardCode, regionData.name)
end
end
local multiregion = {}
if args['detail'] then
if args['detail'] then
for _, champion in ipairs(runeterranChampions) do
for _, data in ipairs(allCards) do
if p.isFromRuneterran{data.code,champion} then
regions["Runeterra"] = (regions["Runeterra"] or 0) + data.count
regions[data.regionName] = (regions[data.regionName] or 0) - data.count
data.regionName = "Runeterra"
end
end
end
for _, data in ipairs(allCards) do
if data.regionName ~= "Runeterra" and lorData[data.code].categoryRefs and lorData[data.code].regions then
for _,ref in ipairs(lorData[data.code].categoryRefs) do
if ref == "Multi-region" then
regions[data.regionName] = (regions[data.regionName] or 0) - data.count
local region1 = data.regionName
local region2 = nil
for _,region in ipairs(lorData[data.code].regions) do
if region1 ~= region then
region2 = region
break
end
end
table.insert(multiregion,{
count=data.count,
region1=region1,
region2=region2
})
break
end
end
end
end
for _,data in pairs(multiregion) do
if not data.region2 or (regions[data.region1] and regions[data.region1] > 0) then
regions[data.region1] = (regions[data.region1] or 0) + data.count
elseif regions[data.region2] and regions[data.region2] > 0 then
regions[data.region2] = (regions[data.region2] or 0) + data.count
else
regions[data.region1] = (regions[data.region1] or 0) + data.count
end
end
for region, count in pairs(regions) do
if count <= 0 then
regions[region] = nil
end
end
return {
return {
shards = totalShards,
shards = totalShards,
Line 201: Line 288:
spellCount = spellCount,
spellCount = spellCount,
landmarkCount = landmarkCount,
landmarkCount = landmarkCount,
equipmentCount = equipmentCount,
rarities = rarities
rarities = rarities
}
}
else
else
for _, champion in ipairs(runeterranChampions) do
for _, data in ipairs(deckList) do
if p.isFromRuneterran{data.code,champion} then
data.region = "Runeterra"
end
end
end
for _, data in ipairs(allCards) do
if lorData[data.code].categoryRefs and lorData[data.code].regions then
for _,ref in ipairs(lorData[data.code].categoryRefs) do
if ref == "Multi-region" then
regions[data.regionName] = (regions[data.regionName] or 0) - data.count
break
end
end
end
end
for _, data in ipairs(deckList) do
if data.region ~= "Runeterra" and lorData[data.code].categoryRefs and lorData[data.code].regions then
for _,ref in ipairs(lorData[data.code].categoryRefs) do
if ref == "Multi-region" then
local region1 = data.region
local region2 = nil
for _,region in ipairs(lorData[data.code].regions) do
if region1 ~= region then
region2 = region
break
end
end
if not region2 or (regions[region1] and regions[region1] > 0) then
data.region = region1
elseif regions[region2] and regions[region2] > 0 then
data.region = region2
else
data.region = region1
end
break
end
end
end
end
return deckList
return deckList
end
end
end

function p.isFromRuneterran(frame)
local args = lib.frameArguments(frame)
local lorData = require("Module:LoRData/data")
local cardCode = args['code'] or args[1] or nil
local region = args['region'] or args['champion'] or args[2] or nil
if not cardCode or not region or not lorData[cardCode] then
return false
end
local card = lorData[cardCode]
if card.supertype == "Champion" then
return false
end
if region == "Aatrox" and card.subtype then
for _, subtype in ipairs(card.subtype) do
if subtype == "Darkin" then
return true
end
end
elseif region == "Bard" then
local CHIME_CARDS = { ["06BC011"] = true, ["06BC026"] = true, ["06BC031"] = true,
["06BC032"] = true, ["06BC044"] = true, ["06MT029"] = true, ["06MT047"] = true,
["08BC005"] = true, ["08SI021"] = true
}
if CHIME_CARDS[cardCode] then return true end
elseif region == "Elder Dragon" then
if card.cost >= 6 then return true end
elseif region == "Evelynn" and card.categoryRefs then
for _, ref in ipairs(card.categoryRefs) do
if ref == "Husk-generating" then
return true
end
end
elseif region == "Jax" and card.subtype then
for _, subtype in ipairs(card.subtype) do
if subtype == "Weaponmaster" then
return true
end
end
elseif region == "Jhin" and card.keywordRefs then
for _, ref in ipairs(card.keywordRefs) do
if ref == "Skill-generating" then
return true
end
end
elseif (region == "Kayn" or region == "Varus") and card.subtype then
for _, subtype in ipairs(card.subtype) do
if subtype == "Cultist" then
return true
end
end
elseif region == "Neeko" and card.subtype then
local SUBTYPES = { ["Bird"] = true, ["Cat"] = true, ["Dog"] = true,
["Elnuk"] = true, ["Fae"] = true, ["Reptile"] = true, ["Spider"] = true
}
for _, subtype in ipairs(card.subtype) do
if SUBTYPES[subtype] then
return true
end
end
elseif region == "Ryze" and card.type == "Spell" and card.keywords then
for _, keyword in ipairs(card.keywords) do
if keyword == "Burst" or keyword == "Focus" then
return true
end
end
elseif region == "The Poro King" then
local PORO_CARDS = { ["01FR016"] = true, ["01FR025"] = true, ["02FR003"] = true,
["03FR018"] = true, ["03PZ018"] = true, ["06BC043"] = true, ["09FR009"] = true,
["99FR001"] = true, ["99FR002"] = true, ["99PZ118"] = true
}
if PORO_CARDS[cardCode] then return true end
if card.subtype then
for _, subtype in ipairs(card.subtype) do
if subtype == "Poro" then
return true
end
end
end
end
return false
end

function p.adventureRegions(frame)
local args = lib.frameArguments(frame)
local str = args[1] or nil
local size = args["size"] or args[2] or "20px"
local sep = args["sep"] or "&nbsp;&nbsp;"
if not str then return nil end -- Exit if no input
local result = {}
local REGION_CODES = {
DE = "Demacia",
FR = "Freljord",
IO = "Ionia",
NX = "Noxus",
PZ = "Piltover and Zaun",
SI = "Shadow Isles",
BW = "Bilgewater",
SH = "Shurima",
MT = "Targon",
BC = "Bandle City",
RU = "Runeterra"
}
for i = 1, #str, 2 do
local pair = str:sub(i, i + 1):upper()
mw.log(pair)
local region = REGION_CODES[pair] or "Neutral"
table.insert(result, "[[File: " .. region .. " LoR Region.png|" .. size .. "|link=]]")
end
return table.concat(result, sep)
end

function p.adventureRewards(frame)
local args = lib.frameArguments(frame)
local step = args["step"] or "5"
step = tonumber(step)
if not step then
return "Step Error."
end
local base_start = "<center><table style=\"border-collapse: collapse;\"><tr>"
local base_end = "</tr></table></center>"
local r = ""
local count = 0
for i, a in ipairs(args) do
count = i
end
local rows = math.ceil(count/step)
local index_lastrow = ((rows-1)*step)+1
for i, a in ipairs(args) do
if i % step == 1 then
if i ~= 1 then
r = r .. base_end
end
r = r .. base_start
if i < index_lastrow then
r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452; border-left: 1px solid #3e4452; border-bottom: 1px solid #3e4452;\"> "
else
r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452; border-left: 1px solid #3e4452;\"> "
end
else
if i < index_lastrow then
r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452; border-bottom: 1px solid #3e4452;\"> "
else
r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452;\"> "
end
end
r = r .. args[i] .. "</td>"
end
r = r .. base_end
return frame:preprocess(r)
end
end


function p.headerDeck(frame)
function p.headerDeck(frame)
local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
local args = lib.frameArguments(frame)
local builder = require("Module:SimpleHTMLBuilder")
args['name'] = args['name'] or 'Untitled Deck'
args['name'] = args['name'] or 'Untitled Deck'
Line 231: Line 523:
local spellCount = deckDetail['spellCount'] or nil
local spellCount = deckDetail['spellCount'] or nil
local landmarkCount = deckDetail['landmarkCount'] or nil
local landmarkCount = deckDetail['landmarkCount'] or nil
local equipmentCount = deckDetail['equipmentCount'] or nil
local rarities = deckDetail['rarities'] or nil
local rarities = deckDetail['rarities'] or nil
local template = mw.html.create('div')
local template = builder.create('div')
:css('position', 'relative')
:css('position', 'relative')
:css('font-family', 'BeaufortLoL')
:css('font-family', 'BeaufortLoL')
Line 241: Line 534:
:done()
:done()
local deckHeader = mw.html.create('div')
local deckHeader = builder.create('div')
:addClass('deck-header')
:addClass('deck-header')
:css('position', 'absolute')
:css('position', 'absolute')
Line 258: Line 551:
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:' .. regionName .. ' LoR Region.png|60px|link=' .. regionName .. ' (Legends of Runeterra)]]')
:wikitext('[[File:' .. regionName .. ' LoR Region.png|60px|link=LoR:' .. regionName .. ']]')
:done()
:done()
end
end
Line 267: Line 560:
:css('color', '#FFE3B0')
:css('color', '#FFE3B0')
:css('font-size', '16px')
:css('font-size', '16px')
:wikitext('[[File:Shard icon.png|40px|link=Shard (Legends of Runeterra)]]' .. Utility.comma_value(totalShards))
:wikitext('[[File:Shard icon.png|40px|link=LoR:Shard]]' .. Utility.comma_value(totalShards))
:done()
:done()
deckHeader:done()
deckHeader:done()
local deckInfo = mw.html.create('div')
local deckInfo = builder.create('div')
:addClass('deck-info')
:addClass('deck-info')
:css('position', 'absolute')
:css('position', 'absolute')
Line 296: Line 589:
:addClass('deck-info')
:addClass('deck-info')
:css('position', 'absolute')
:css('position', 'absolute')
:css('top', '170px')
:css('top', '240px')
:css('left', '20px')
:css('left', '15px')
:css('font-size', '20px')
:css('font-size', '20px')
:css('color', '#FFE3B0')
:css('color', '#FFE3B0')
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Follower card.svg|25px|link=Follower (Legends of Runeterra)]]' .. followerCount .. '&nbsp;')
:wikitext('[[File:Follower card.png|25px|link=LoR:Follower]]' .. followerCount .. '&nbsp;')
:done()
:done()
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Spell card.svg|25px|link=Spell (Legends of Runeterra)]]' .. spellCount .. '&nbsp;')
:wikitext('[[File:Spell card.png|25px|link=LoR:Spell]]' .. spellCount .. '&nbsp;')
:done()
:done()
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Keyword Landmark.png|25px|link=Landmark (Legends of Runeterra)]]' .. landmarkCount .. '&nbsp;')
:wikitext('[[File:Keyword Landmark.png|25px|link=LoR:Landmark]]' .. landmarkCount .. '&nbsp;')
:done()
:tag('span')
:addClass('inline-image')
:wikitext('[[File:Keyword Equipment.png|25px|link=LoR:Equipment]]' .. equipmentCount .. '&nbsp;')
:done()
:done()
:tag('br')
:tag('br')
Line 316: Line 613:
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Common rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[1] or 0) .. '&nbsp;')
:wikitext('[[File:LoR Common icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[1] or 0) .. '&nbsp;')
:done()
:done()
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Rare rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[2] or 0) .. '&nbsp;')
:wikitext('[[File:LoR Rare icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[2] or 0) .. '&nbsp;')
:done()
:done()
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Epic rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[3] or 0) .. '&nbsp;')
:wikitext('[[File:LoR Epic icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[3] or 0) .. '&nbsp;')
:done()
:done()
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Champion rarity.svg|25px|link=Card_types_(Legends_of_Runeterra)#By_rarity]]' .. (rarities[4] or 0) .. '&nbsp;')
:wikitext('[[File:LoR Champion icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[4] or 0) .. '&nbsp;')
:done()
:done()
:done()
:done()
if #champions then
if #champions then
local deckChampions = mw.html.create('div')
local deckChampions = builder.create('div')
:addClass('deck-champions')
:addClass('deck-champions')
:css('position', 'absolute')
:css('position', 'absolute')
:css('bottom', '20px')
:css('bottom', '20px')
:css('left', '20px')
:css('left', '200px')
:css('font-size', '20px')
:css('font-size', '20px')
:css('color', '#FFE3B0')
:css('color', '#FFE3B0')
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:Champion card.svg|25px|link=Champion (Legends of Runeterra)]]')
:wikitext('[[File:Champion card.png|25px|link=LoR:Champion]]')
:done()
:done()
for _, deckData in pairs(champions) do
for _, deckData in pairs(champions) do
Line 348: Line 645:
:tag('span')
:tag('span')
:addClass('inline-image')
:addClass('inline-image')
:wikitext('[[File:' .. deckData.code .. '.png|60px|link='.. deckData.code .. ' (Legends of Runeterra)]]')
:wikitext('[[File:' .. deckData.code .. '.png|60px|link=LoR:'.. deckData.code .. ']]')
:done()
:done()
:tag('span')
:tag('span')
Line 367: Line 664:


function p.deckDecoder(frame)
function p.deckDecoder(frame)
local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
local args = lib.frameArguments(frame)
if args['code'] == nil then
if args['code'] == nil then
Line 377: Line 674:
if Utility.table_contains(groupByValues, groupBy) == false then
if Utility.table_contains(groupByValues, groupBy) == false then
return 'Only support Group By "region", "type" or none'
return 'Only support Group By "region", "type" or none'
end
local header = args['header'] or true
if header == 'false' then
header = false
end
end
Line 391: Line 693:
groupByKey = deckData.region
groupByKey = deckData.region
elseif groupBy == 'type' then
elseif groupBy == 'type' then
groupByKey = cardType .. 's'
groupByKey = deckData.type .. 's'
else
else
groupByKey = 'List'
groupByKey = 'List'
Line 418: Line 720:
local template = ""
local template = ""
if header then
template = template .. p.headerDeck{code = args['code'], name = args['name'], description = args['description']}
template = template .. p.headerDeck{code = args['code'], name = args['name'], description = args['description']}
end
local newline = "\n"
local newline = "\n"
Line 471: Line 775:


return frame:preprocess(template)
return frame:preprocess(template)
end

function p.pocHeaderDeck(frame)
local args = lib.frameArguments(frame)
local builder = require("Module:SimpleHTMLBuilder")
local adventureData = require("Module:LoRPoCData/adventure")
local lorData = require("Module:LoRData/data")
args['name'] = args['name'] or args['foe'] or 'Untitled Deck'
local cmp = args['campaign'] or "World Adventures"
if args['adventure'] == nil or not adventureData[cmp][args['adventure']] then
return 'Adventure missing or invalid'
end
local adventure = adventureData[cmp][args['adventure']]
if args['foe'] == nil or not adventure[args['foe']] then
return 'Foe missing or invalid'
end
local foe = adventure[args['foe']]
local stars = adventure.stars or false
local adventureType = adventure["type"] or false
args['code'] = args['code'] or foe.deck or nil
if args['code'] == nil then
return 'Empty Code'
end
local hasPower = foe.power or false
local hasPowerArgs = args['powername'] or args['powerdesc'] or false
local powername = nil
local powerdesc = nil
local powericon = nil
local powerrarity = nil
if hasPower then
powername = foe.power.name or 'Power Name'
powerdesc = foe.power.desc or 'Power Description.'
powericon = foe.power.icon or nil
powerrarity = foe.power.rarity or 'Common'
end
if hasPowerArgs then
powername = args['powername'] or powername or 'Power Name'
powerdesc = args['powerdesc'] or powerdesc or 'Power Description.'
powericon = args['powericon'] or powericon or nil
powerrarity = args['powerrarity'] or powerrarity or 'Common'
hasPower = true
end
local deckDetail = p.deckDataFromCode{code = args['code'], detail = true}
if type(deckDetail) ~= 'table' then
return deckDetail
end
local deckSimple = p.deckDataFromCode{code = args['code'], detail = false}
if type(deckSimple) ~= 'table' then
return deckSimple
end
local topUnitCode = args['topUnitCode'] or foe.code or 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 equipmentCount = deckDetail['equipmentCount'] or nil
local MAX_MOBILE_ITEMS = 3
local MAX_ITEMS = 7
--Extract items on cards
local items = foe.items or nil
--Fuse champions with items
local championsItem = {}
if items then
for code, data in pairs(items) do
if lorData[code] and lorData[code]["supertype"] == "Champion" then
table.insert(championsItem,{
code=code,
count=0,
items=data
})
end
end
end
for _, deckData in pairs(champions) do
local found = false
for _, data in pairs(championsItem) do
if data.code == deckData.code then
found = true
data.count=deckData.count
break
end
end
if not found then
table.insert(championsItem, {
code=deckData.code,
count=deckData.count,
items=nil
})
end
end
table.sort(championsItem, function(a, b)
if (lorData[a.code].name == args['foe']) ~= (lorData[b.code].name == args['foe']) then -- 1. Priority if "name" is the foe
return lorData[a.code].name == args['foe']
end
local aitems = {}
local bitems = {}
if a.items then aitems = a.items end
if b.items then bitems = b.items end
if #aitems ~= #bitems then -- 2. Sort by the number of items (higher count first)
return #aitems > #bitems
end
if lorData[a.code].cost ~= lorData[b.code].cost then -- 3. Sort by "cost" (lowest to highest)
return lorData[a.code].cost < lorData[b.code].cost
end
return lorData[a.code].name < lorData[b.code].name -- 4. Sort alphabetically by "name"
end)
--detailed cards with items list
local detailedItems = {}
if items then
for code, data in pairs(items) do
table.insert(detailedItems, {
code=code,
name=lorData[code].name,
cost=lorData[code].cost,
items=data
})
end
end
table.sort(detailedItems, function(a, b)
if (a.name == args['foe']) ~= (b.name == args['foe']) then -- 1. Priority if "name" is the foe
return a.name == args['foe']
end
if #a.items ~= #b.items then -- 2. Sort by the number of items (higher count first)
return #a.items > #b.items
end
if a.cost ~= b.cost then -- 3. Sort by "cost" (lowest to highest)
return a.cost < b.cost
end
return a.name < b.name -- 4. Sort alphabetically by "name"
end)
local template = builder.create('div')
:css('display', 'inline-block')
:css('position', 'relative')
:css('font-family', 'BeaufortLoL')
:tag('div')
:css('opacity', '0.5')
:wikitext('[[File:' .. topUnitCode .. '-full.png|800px|link=]]')
:done()
local deckHeader = builder.create('div')
:addClass('deck-header')
:css('position', 'absolute')
:css('max-width', '650px')
: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()
local regionSize = 60
local count = 0
for _ in pairs(regions) do
count = count + 1
if count > 9 then
regionSize = 50
break
end
end
for regionName, regionCount in pairs(regions) do
deckHeader
:tag('span')
:addClass('inline-image')
:wikitext('[[File:' .. regionName .. ' LoR Region.png|' .. tostring(regionSize) .. 'px|link=LoR:' .. regionName .. ']]')
:done()
end
if stars then
local starHeader= builder.create('div')
:addClass('deck-header')
:css('position', 'absolute')
:css('top', '10px')
:css('left', '730px')
:tag('span')
:css('font-weight', 'bold')
:css('color', 'white')
:css('font-size', '18px')
:tag('span')
:addClass('PoC-Star-Difficulty')
:css('white-space', 'nowrap')
:tag('span')
:css('position', 'relative')
:css('text-indent', '0')
:css('top', '-3px')
:wikitext('[[File:PoC Star icon.png|60px|link=]]')
:tag('span')
:css('position', 'absolute')
:css('left', '17px')
:css('top', '30%')
:css('user-select', 'none')
:css('line-height', '1.5')
:css('text-align', 'center')
:css('width', '25px')
:css('height', '25px')
:css('transform', 'translateY(-25%)')
:css('text-shadow', '0.5px 0.5px 1px #000, 0.5px 0.5px 1px #000, -0.5px -0.5px 1px #000, -0.5px -0.5px 1px #000, 0.5px -0.5px 1px #000, 0.5px -0.5px 1px #000, -0.5px 0.5px 1px #000, -0.5px 0.5px 1px #000, 0.5px 0.5px 1px #000, 0.5px 0.5px 1px #000;')
:wikitext(stars)
:done()
:done()
:done()
:done()
template
:node(starHeader)
end
if adventureType then
local leftOffset = '660px'
if not stars then leftOffset = '730px' end
local typeImage = ''
if adventureData.Icons[adventureType] then
typeImage = '[[File:' .. adventureData.Icons[adventureType] .. '|60x60px|link=LoR:World Adventures]]'
end
local typeHeader= builder.create('div')
:addClass('deck-header')
:css('position', 'absolute')
:css('top', '8px')
:css('left', leftOffset)
:tag('span')
:wikitext(typeImage)
:done()
template
:node(typeHeader)
end
if #championsItem > 0 then
local deckChampions = builder.create('div')
:addClass('deck-champions')
:css('position', 'absolute')
:css('top', '70px')
:css('left', '20px')
:css('font-size', '20px')
:css('color', '#FFE3B0')
:tag('span')
:addClass('inline-image')
:wikitext('[[File:Champion card.png|25px|link=LoR:Champion]]')
:done()
local n = 0
for _, deckData in pairs(championsItem) do
if (n == MAX_MOBILE_ITEMS and args['mobile']) or (n == MAX_ITEMS) then
deckChampions
:tag('span')
:css('font-weight', 'bold')
:css('font-size', '30px')
:addClass('inline-image')
:wikitext(frame:preprocess(' ...'))
:done()
break
end
if deckData.items then
local s = '{{PoCCardItems|size=70|' .. deckData.code
for _, item in pairs(deckData.items) do
s = s .. '|' .. item
end
s = s .. '}}'
deckChampions
:tag('span')
:addClass('inline-image')
:wikitext(frame:preprocess(s))
:done()
:tag('span')
:addClass('inline-image')
:wikitext(' x' .. deckData.count .. '&nbsp;')
:done()
else
deckChampions
:tag('span')
:addClass('inline-image')
:wikitext('[[File:' .. deckData.code .. '.png|70px|link=LoR:'.. deckData.code .. ']]')
:done()
:tag('span')
:addClass('inline-image')
:wikitext('x' .. deckData.count .. '&nbsp;')
:done()
end
n = n+1
end
deckChampions:
done()
template
:node(deckChampions)
end
if items then
local deckItems = builder.create('div')
:addClass('deck-champions')
:css('position', 'absolute')
:css('top', '175px')
:css('left', '20px')
:css('font-size', '20px')
:css('color', '#FFE3B0')
:tag('span')
:addClass('inline-image')
:wikitext('[[File:PoC Relic icon.png|25px]]')
:done()
local n = 0
for _, itemList in ipairs(detailedItems) do
if (n == MAX_MOBILE_ITEMS and args['mobile']) or (n == MAX_ITEMS) then
deckItems
:tag('span')
:css('font-weight', 'bold')
:css('font-size', '30px')
:addClass('inline-image')
:wikitext(frame:preprocess(' ...'))
:done()
break
end
local count = 0;
local nonChampion = true
for _, data in pairs(championsItem) do
if data.code == itemList.code then
nonChampion = false
break
end
end
if nonChampion then
for _, data in pairs(deckSimple) do
if data.code == itemList.code then
count = data.count
break
end
end
local s = '{{PoCCardItems|size=70|' .. itemList.code
for _, item in ipairs(itemList.items) do
s = s .. '|' .. item
end
s = s .. '}}'
deckItems
:tag('span')
:addClass('inline-image')
:wikitext(frame:preprocess(s))
:done()
:tag('span')
:addClass('inline-image')
:wikitext(' x' .. count .. '&nbsp;')
:done()
n = n+1
end
end
deckItems:
done()
template
:node(deckItems)
end
local powerImage
local powerTitle
local powerInfo
if hasPower then
local image_leftOffset = 230
if args['mobile'] then image_leftOffset = 200 end
powerImage = builder.create('div')
:addClass('deck-info')
:css('position', 'absolute')
:css('top', '300px')
:css('left', tostring(image_leftOffset) .. 'px')
if powericon then
powerImage
:tag('span')
:css('display', 'inline-block')
:wikitext(frame:preprocess('{{PoCPowerIconOverlay|' .. powericon .. '|' .. powerrarity .. '|80}}'))
:done()
end
powerImage
:done()
powerTitle = builder.create('div')
:addClass('deck-info')
:css('position', 'absolute')
:css('top', '300px')
:css('left', tostring(image_leftOffset+100) .. 'px')
:css('color', '#FFE3B0')
:css('font-weight', 'bold')
:tag('span')
:css('display', 'inline-block')
:css('font-size', '16px')
:wikitext(powername)
:done()
powerTitle:done()
powerInfo = builder.create('div')
:addClass('deck-info')
:css('position', 'absolute')
:css('top', '330px')
:css('left', tostring(image_leftOffset+100) .. 'px')
:css('color', '#FFE3B0')
:tag('span')
:css('display', 'inline-block')
:css('max-width', '460px')
:css('font-size', '14px')
:wikitext(frame:preprocess(powerdesc))
:done()
powerInfo:done()
end
local health = foe.health or '?'
local mana = foe.mana or '?'
local hand = foe.hand or '?'
local vicious = foe.vicious or false
if hasPower then
template
:node(deckHeader)
:node(powerImage)
:node(powerTitle)
:node(powerInfo)
else
template
:node(deckHeader)
end

local color = '#FFE3B0'
if vicious then
color = '#FF7474'
if tonumber(health) then health = tostring(tonumber(health) + 10) end
if tonumber(mana) then mana = tostring(tonumber(mana) + 1) end
if tonumber(hand) then hand = tostring(tonumber(hand) + 1) end
end
template
:tag('div')
:addClass('deck-info')
:css('position', 'absolute')
:css('top', '320px')
:css('left', '15px')
:css('font-size', '20px')
:css('color', '#FFE3B0')
:tag('span')
:addClass('inline-image')
:wikitext('[[File:Follower card.png|25px|link=LoR:Follower]]' .. followerCount .. '&nbsp;')
:done()
:tag('span')
:addClass('inline-image')
:wikitext('[[File:Spell card.png|25px|link=LoR:Spell]]' .. spellCount .. '&nbsp;')
:done()
:tag('span')
:addClass('inline-image')
:wikitext('[[File:Keyword Landmark.png|25px|link=LoR:Landmark]]' .. landmarkCount .. '&nbsp;')
:done()
:tag('span')
:addClass('inline-image')
:wikitext('[[File:Keyword Equipment.png|25px|link=LoR:Equipment]]' .. equipmentCount .. '&nbsp;')
:done()
:done()
:tag('div')
:addClass('deck-info')
:css('position', 'absolute')
:css('top', '360px')
:css('left', '15px')
:css('font-size', '20px')
:css('color', color)
:tag('span')
:addClass('inline-image')
:wikitext('[[File:PoC Health icon.png|25px|link=LoR:Nexus Health]]' .. health .. '&nbsp;')
:done()
:tag('span')
:addClass('inline-image')
:wikitext('[[File:LoR mana icon alt.png|25px|link=LoR:Terminology#Mana]]' .. mana .. '&nbsp;')
:done()
:tag('span')
:addClass('inline-image')
:wikitext('[[File:Keyword Draw.png|25px|link=]]' .. hand .. '&nbsp;')
:done()
:tag('span')
:addClass('inline-image')
:wikitext(lib.ternary(vicious,frame:preprocess(' {{PoCPowerIconOverlay|04SI017-full.png|Special|30}}'),''))
:done()
:done()
template:allDone()
return tostring(template)
end

function p.pocDeckDecoder(frame)
local args = lib.frameArguments(frame)
local adventureData = require("Module:LoRPoCData/adventure")
local cmp = args['campaign'] or "World Adventures"
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
if args['adventure'] == nil or not adventureData[cmp][args['adventure']] then
return 'Adventure missing or invalid'
end
local adventure = adventureData[cmp][args['adventure']]
if args['foe'] == nil or not adventure[args['foe']] then
return 'Foe missing or invalid'
end
local foe = adventure[args['foe']]
local items = foe.items or {}
local deckItems = {}
args['code'] = args['code'] or foe.deck or nil
if args['code'] == nil then
return 'Empty Code'
end
local header = args['header'] or true
if header == 'false' then
header = false
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 = deckData.type .. 's'
else
groupByKey = 'List'
end
if result[groupByKey] == nil then
result[groupByKey] = {}
end
if items[deckData.code] then
table.insert(deckItems,deckData.code)
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 = ""
if header then
template = template .. p.pocHeaderDeck(frame)
end
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:''' <span style='word-wrap: break-word;'>" .. args['code'] .. '</span>' .. 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 = ""
local isFirst = true
for _, cardData in ipairs(regionData) do
if cardData.type ~= type then
if (type ~= "") then
template = template .. "|}" .. newline
end
template = template .. "{| class=\"wikitable\"" .. newline .. "! colspan=5 style=\"background-color: #374b65; text-align: left;\" |" .. cardData.type .. "s" .. newline .. "|-" .. newline
isFirst = true
type = cardData.type
end
local cardCodeTemplate = ""
if isFirst then
cardCodeTemplate = cardCodeTemplate .. '|-' .. newline
isFirst = false
else
cardCodeTemplate = cardCodeTemplate .. '|' .. newline
isFirst = true
end
cardCodeTemplate = cardCodeTemplate .."| {{LoR|" .. cardData.name
if cardData.type ~= "Champion" then
cardCodeTemplate = cardCodeTemplate .. "|code=" .. cardData.code
end
cardCodeTemplate = cardCodeTemplate .. "}}" .. " (x" .. cardData.count .. ")" .. newline
cardCodeTemplate = cardCodeTemplate .. "| "
if items[cardData.code] then
for _, item in ipairs(items[cardData.code]) do
if item:sub(1, 1) == "I" then
cardCodeTemplate = cardCodeTemplate .. "{{PoC|item|code=" .. item .. "}} "
else
cardCodeTemplate = cardCodeTemplate .. "{{PoC|relic|code=" .. item .. "}} "
end
end
end
template = template .. cardCodeTemplate .. newline
end
template = template .. "|}" .. newline
end
local extraItems = ""
local isFirst = true
for key, itemList in pairs(items) do
if true then
end
if not Utility.table_contains(deckItems,key) then
if isFirst then
extraItems = extraItems .. '|-' .. newline
isFirst = false
else
extraItems = extraItems .. '|' .. newline
isFirst = true
end
extraItems = extraItems .. "| {{LoR|code=" .. key .. "}}" .. newline .. "| "
for _, item in ipairs(itemList) do
if item:sub(1, 1) == "I" then
extraItems = extraItems .. "{{PoC|item|code=" .. item .. "}} "
else
extraItems = extraItems .. "{{PoC|relic|code=" .. item .. "}} "
end
end
extraItems = extraItems .. newline
end
end
if extraItems ~= "" then
if groupBy == 'region' or groupBy == 'type' then
template = template .. "|-|" .. newline .. "Non-deck=" .. newline .. "'''Code:''' " .. args['code'] .. newline
else
-- template = template .. ";Non-deck cards with items" .. newline
end
template = template .. "{| class=\"wikitable\"" .. newline .. "! colspan=5 style=\"background-color: #374b65; text-align: left;\" | Non-deck cards with items" .. newline .. "|-" .. newline .. extraItems .. "|}" .. newline
end
template = template .. "</tabber>"

return frame:preprocess(template)
end

function p.championRoster(frame)
local args = lib.frameArguments(frame)
local size = args["size"] or "120px"
local lorData = require('Module:LoRData/data')
local lorTable = {}
local s = ""
local REGION_CODES = {
DE = "Demacia",
FR = "Freljord",
IO = "Ionia",
NX = "Noxus",
PZ = "Piltover and Zaun",
SI = "Shadow Isles",
BW = "Bilgewater",
SH = "Shurima",
MT = "Targon",
BC = "Bandle City",
RU = "Runeterra"
}
local cardtable = {}
for cardcode in pairs(lorData) do
table.insert(cardtable, cardcode)
end
table.sort(cardtable)
local championtable = {}
for i, cardcode in pairs(cardtable) do
repeat
local t = lorData[cardcode]
if t.rarity ~= 'Champion' then
break
end
t['code'] = cardcode
table.insert(championtable, t)
break
until true
end
-- END
table.sort(championtable, function(a, b) return a['name'] < b['name'] end)
local rosterText = '' -- Temporarily will be class poc-roster
rosterText = rosterText .. '<div class="poc-roster-container" style="display: flex; flex-direction: column; margin: 0 auto; padding: 5px; text-align: center;">'
rosterText = rosterText .. '<div class="poc-roster" style="display: flex; justify-content: space-evenly; flex-wrap: wrap;">'
for _, card in pairs(championtable) do
rosterText = rosterText .. '<div class="poc-roster-champion" style="padding:0.5em;" data-champion="' .. card.name .. '" data-searchname="' .. card.name .. '" data-game="lor"'
if card.regions then
rosterText = rosterText .. ' data-region="' .. table.concat(card.regions, ", ") .. '">'
else
rosterText = rosterText .. ' data-region="' .. REGION_CODES[card.code:sub(3,4):upper()] .. '">'
end
rosterText = rosterText .. '[[File:' .. card.code .. '.png|' .. size .. '|link=LoR:' .. card.name .. ']] <br> [[LoR:' .. card.name .. '|' .. card.name .. ']]</div>'
end
rosterText = rosterText .. '</div>'
rosterText = rosterText .. '</div>'
return rosterText
end

function p.championRosterPoC(frame)
local args = lib.frameArguments(frame)
local size = args["size"] or "120px"
local pocData = require('Module:LoRPoCData/data')
local lorTable = {}
local s = ""
local rosterText = ''
rosterText = rosterText .. '<div class="poc-roster-container" style="display: flex; flex-direction: column; margin: 0 auto; padding: 5px; text-align: center;">'
rosterText = rosterText .. '<div class="poc-roster" style="display: flex; justify-content: space-evenly; flex-wrap: wrap;">'
pocData = pocData["Champion"]
local championtable = {}
for champion, data in pairs(pocData) do
if (data["playable"]) then
table.insert(championtable, data)
end
end
table.sort(championtable, function(a, b) return a.name < b.name end)
for _, data in pairs(championtable) do
rosterText = rosterText .. '<div class="poc-roster-champion" style="padding:0.5em;" data-champion="' .. data.name .. '" data-searchname="' .. data.name .. '" '
rosterText = rosterText .. 'data-game="lor" data-playstyle="' .. (data.playable.style or "no") .. '" data-difficulty="' .. (data.playable.difficulty or "no") .. '" '
rosterText = rosterText .. 'data-region="' .. table.concat(data.playable.region, ", ") .. '" '
if data.playable.constellationRegion then
rosterText = rosterText ..'data-constellation="' .. data.playable.constellationRegion .. ', hasConstellation" '
else
rosterText = rosterText ..'data-constellation="constellation-none" '
end
rosterText = rosterText .. '>[[File:' .. data.id .. '.png|' .. size .. '|link=LoR:' .. data.name .. '/PoC]] <br> [[LoR:' .. data.name .. '/PoC|' .. data.name .. ']]</div>'
end
rosterText = rosterText .. '</div>'
rosterText = rosterText .. '</div>'
return rosterText
end

function p.getPoCChampionGrid(frame)
-- Author: @Bladenite
local args = lib.frameArguments(frame)
local size = args["size"] or "120px"
local pocData = require('Module:LoRPoCData/data')
local gridText = ''
-- Creating outer container
gridText = gridText .. '<div class="poc-champions-flexbox-container" style="display: flex; flex-wrap: wrap; gap: 7.5px; justify-content: space-around;">'
-- Getting playable champions data
pocData = pocData["Champion"]
local championtable = {}
for champion, data in pairs(pocData) do
if (data["playable"]) then
table.insert(championtable, data)
end
end
table.sort(championtable, function(a, b) return a.name < b.name end)
for _, data in pairs(championtable) do
gridText = gridText .. '<div class="poc-champion-container" '
-- Adding data for search parameters
gridText = gridText .. 'data-champion="' .. data.name .. '" data-searchname="' .. data.name .. '" '
gridText = gridText .. 'data-game="lor" data-playstyle="' .. (data.playable.style or "no") .. '" data-difficulty="' .. (data.playable.difficulty or "no") .. '" '
gridText = gridText .. 'data-region="' .. table.concat(data.playable.region, ", ") .. '" '
if data.playable.constellationRegion then
gridText = gridText ..'data-constellation="' .. data.playable.constellationRegion .. ', hasConstellation" '
else
gridText = gridText ..'data-constellation="constellation-none" '
end
gridText = gridText .. '>' --closing div
-- Building champion board
gridText = gridText .. '<div class="poc-champion-circle"> [[File:' .. data.id .. '-circle.png|75px|link=LoR:' .. data.name .. '/PoC]]</div>'
gridText = gridText .. '<div class="poc-champion-name"> [[LoR:' .. data.name .. '/PoC|' .. data.name .. ']]</div>'
gridText = gridText .. '<div class="poc-champion-region">'
if #data.playable.region == 1 then
gridText = gridText .. '[[File:' .. data.playable.region[1] .. ' LoR Region.png|35px|link=]]</div>'
elseif #data.playable.region == 2 then
gridText = gridText .. '[[File:' .. data.playable.region[1] .. ' LoR Region.png|35px|link=]] '
gridText = gridText .. '[[File:' .. data.playable.region[2] .. ' LoR Region.png|35px|link=]]</div>'
end
if data.playable.style then
gridText = gridText .. '<div class="poc-champion-info"><span class="poc-' .. data.playable.style:lower() .. '">[[File:' .. data.playable.style .. ' LoR icon.png|20px|link=]] ' .. data.playable.style .. '</span> • '
end
if data.playable.difficulty then
gridText = gridText .. '<span class="poc-' .. data.playable.difficulty:lower() .. '"> ' .. data.playable.difficulty .. '</span></div>'
end
gridText = gridText .. '<div class="poc-champion-power" style="display: inline-flex; flex-wrap: wrap; justify-content: center;'
if data.name ~= 'Aurelion Sol' then
gridText = gridText .. 'width: 120px;' -- Controls the number of powers in one line (120px -> 3 powers per line)
end
gridText = gridText .. '>'
-- Ordering the powers correctly
for i, power in ipairs(data.playable.power) do
if power.code then
gridText = gridText .. '<span class="skin-icon" style="white-space: nowrap; order: ' .. tostring(i) .. ';"'
gridText = gridText .. ' data-game="lor" data-skin="PoC" data-champion="' .. power.code .. '"><span style="position:relative;text-indent:0;">'
gridText = gridText .. '[[File:' .. power.code .. '.png|30px|link=LoR:' .. data.name .. '/PoC]]&#8202;&#8202;</span></span>'
end
end
gridText = gridText .. '</div>'
if data.support then
gridText = gridText .. '<div class="poc-champion-reinforcement"><span class="lor-tooltip" data-param="' .. data.id .. ',' .. data.support[1] .. ',' .. data.support[2] .. ', 50%">[[File:LoR Card icon.png|17px|link=|alt=Reinforcements]]</span></div>'
end
-- Closing champion container
gridText = gridText .. '</div>'
end
-- Closing grid container
gridText = gridText .. '</div>'
return gridText
end

function p.pocChampionNavigation(frame)
local args = lib.frameArguments(frame)
local size = args["size"] or "80px"
local constellationOnly = args["constellationOnly"] or false
local pocData = require('Module:LoRPoCData/data')
local lorTable = {}
local s = ""
local navText = ""
pocData = pocData["Champion"]
local championtable = {}
for champion, data in pairs(pocData) do
if constellationOnly ~= false then
if (data["playable"]) and (data["playable"]["constellationRegion"]) then
table.insert(championtable, data)
end
else
if (data["playable"]) then
table.insert(championtable, data)
end
end
end
table.sort(championtable, function(a, b) return a.name < b.name end)
for _, data in pairs(championtable) do
navText = navText .. '[[File:' .. data.id .. '.png|' .. size .. '|link=LoR:' .. data.name .. '/PoC]]'
end
return navText
end

function p.pocWeekliesDateRange()
local today = os.date("*t")
local current_wday = today.wday
local days_to_add = (2 - current_wday) % 7 -- 2 corresponds to Monday
if days_to_add == 0 then
days_to_add = 7
end
days_to_add = days_to_add - 1 -- Last day is non-inclusive
local next_sunday_time = os.time(today) + days_to_add * 86400 -- 86400 seconds in a day
local next_sunday = os.date("*t", next_sunday_time)
local next_month_name = os.date("%B", next_sunday_time)
local prev_monday_time = next_sunday_time - 6 * 86400
local prev_monday = os.date("*t", prev_monday_time)
local prev_month_name = os.date("%B", prev_monday_time)
return string.format("%s %d – %s %d", prev_month_name, prev_monday.day, next_month_name, next_sunday.day)
end
end


function p.subtypeContent(frame)
function p.subtypeContent(frame)
local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
local args = lib.frameArguments(frame)
local cardcode = args['code'] or args[1] or nil
local cardcode = args['code'] or args[1] or nil
Line 486: Line 1,709:
subtypetext = ''
subtypetext = ''
for _, subtype in pairs(lorData[cardcode].subtype) do
for _, subtype in pairs(lorData[cardcode].subtype) do
subtypelink = '[[' .. subtype .. ' (Legends of Runeterra)|' .. subtype .. ']]'
subtypelink = '[[LoR:' .. subtype .. '|' .. subtype .. ']]'
if subtypetext ~= '' then
if subtypetext ~= '' then
subtypetext = subtypetext .. ", " .. subtypelink
subtypetext = subtypetext .. ", " .. subtypelink
Line 497: Line 1,720:
return frame:preprocess(subtypetext)
return frame:preprocess(subtypetext)
end

function p.regionContent(frame)
local args = lib.frameArguments(frame)
local cardcode = args['code'] or args[1] or nil
if not cardcode then
return ''
end
local lorData = mw.loadData('Module:LoRData/data')
if not lorData[cardcode] then
return ''
end
local regions = {}
if not lorData[cardcode].regions then
local regionCode = string.sub(cardcode, 3, 4)
for _, regionData in pairs(REGIONS) do
if regionData.code == regionCode then
table.insert(regions, regionData.name)
break
end
end
else
regions = lorData[cardcode].regions
end
regiontext = ''
for _, region in pairs(regions) do
regionlink = '[[File:' .. region .. ' LoR Region.png|20px]] '
regionlink = regionlink .. '[[LoR:' .. region .. '|' .. region .. ']]'
if regiontext ~= '' then
regiontext = regiontext .. ", " .. regionlink
else
regiontext = regiontext .. regionlink
end
regiontext = regiontext .. "[[" .. "Category:LoR " .. region .. "]]"
end
if regiontext == '' then
regiontext = 'N/A'
end
return frame:preprocess(regiontext)
end
end


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


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

for _, artwork in pairs(lorData[cardcode].artworks) do
for _, artwork in pairs(lorData[cardcode].artworks or {}) do
artworkContent = 'Another Artwork'
local extension = 'png'
local extension = 'png'
local artworkParts = lib.split(artwork, "-")
local HD = false
local alternate = false
local display = false
local prerelease = false
local old = false
for k, v in pairs(artworkParts) do
if (v == 'hd') then
HD = true
end
if (v == 'alt') then
alternate = true
end
if (v == 'display') then
display = true
end
if (v == 'pre') then
prerelease = true
end
if (v == 'old') then
old = true
end
end
local artworkContents = {}
if HD then
table.insert(artworkContents, 'HD')
extension = 'jpg'
end
if prerelease then
table.insert(artworkContents, 'Pre-Release')
end
if alternate then
table.insert(artworkContents, '[[LoR:List of cards/Alternate|Censored]]')
end
if display then
table.insert(artworkContents, '[[League Displays]]')
extension = 'jpg'
end
if old then
table.insert(artworkContents, 'Old')
end
if #artworkContents >= 1 then
artworkContent = table.concat(artworkContents, " ")
else
artworkContent = 'Uncategorized'
end


artworkLink = "<li style='display:inline-block;text-indent:0px;'>[[File:" .. cardcode .. "-"
if artwork == 'hd' then
.. artwork .. "-full." .. extension .. "|thumb|" .. imageSize .. "|" .. artworkContent .. "|left]]</li>"
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
if artworkText ~= '' then
Line 542: Line 1,856:
return frame:preprocess(artworkText)
return frame:preprocess(artworkText)
end

function p.artworkTooltip(frame)
local args = lib.frameArguments(frame)
local artworkCode = args['code'] or args[1] or nil
if not artworkCode then
return ''
end
local artworkList = lib.split(artworkCode, ",")
local width = 250
if string.find(artworkList[1], '-full') then
width = 300
end

if string.find(artworkList[#artworkList], '%%') then
width = math.floor(width * (tonumber(string.match(artworkList[#artworkList], '%d+')/100) or 1))
table.remove(artworkList, #artworkList)
end
if (#artworkList > 4) then
width = width / 2
end
local artworkTooltip = '<div style="display:inline-flex; flex-wrap:wrap; width:' .. math.min(width * 4, width * #artworkList) .. 'px; box-shadow: rgb(1, 10, 19) 0px 0px 25px 15px;">'
for k, v in pairs(artworkList) do
artworkTooltip = artworkTooltip .. '<div style="display:inline-block; background-color: rgb(1, 10, 19); flex: 1 1;">'
artworkTooltip = artworkTooltip .. '[[File:' .. v .. '.png|' .. width .. 'px]]'
artworkTooltip = artworkTooltip .. '</div>'
end
artworkTooltip = artworkTooltip .. '</div>'
return artworkTooltip
end

function p.randomCard(frame)
local args = lib.frameArguments(frame)
local lorData = mw.loadData('Module:LoRData/data')
local keys = {}
for key, _ in pairs(lorData) do
keys[#keys+1] = key --Store keys in another table
end
local randomCode
-- Random
math.randomseed(os.time())
math.random(); math.random(); math.random()
for x = 1,10 do
randomCode = keys[math.random(1, #keys)]
end

local randomCard = lorData[randomCode]
local link = 'LoR:' .. randomCode
local text = '[[File:' .. randomCode .. '.png|250px|link=' .. link .. ']]'
text = text .. '[[' .. link .. '|' .. randomCard.name .. ']]'
local cardMode = randomCard.mode
if cardMode then
text = text .. ' ([[LoR:List of cards/' .. cardMode .. '|' .. cardMode .. ']])'
end
return frame:preprocess(text)
end
end


Line 556: Line 1,940:
bytesPopped = bytesPopped + 1
bytesPopped = bytesPopped + 1
local current = BitOperator.AND(bytes[i], VarInt.AllButMSB)
local current = BitOperator.AND(bytes[i], VarInt.AllButMSB)
result = BitOperator.OR(result, BitOperator.RIGHT_SHIFT(current, currentShift))
result = BitOperator.OR(result, BitOperator.LEFT_SHIFT(current, currentShift))


if BitOperator.AND(bytes[i], VarInt.JustMSB) ~= VarInt.JustMSB then
if BitOperator.AND(bytes[i], VarInt.JustMSB) ~= VarInt.JustMSB then

Latest revision as of 15:11, 9 April 2025

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

-- <pre>

local lib               = require('Module:Feature')
local MAX_KNOWN_VERSION = 5
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"},
    [8]  = {code = "SH", name = "Shurima"},
    [10] = {code = "MT", name = "Targon"},
    [11] = {code = "BC", name = "Bandle City"},
    [13] = {code = "RU", name = "Runeterra"}
}

--To test debug this line mw.executeFunction(p.test)
function p.test(frame)
	output = p.adventureRegions{"RUIODE--"}
	mw.log(output)
end

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 = lib.frameArguments(frame)
    
    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 equipmentCount = 0
    local rarities = {}
    local champions = {}
    local shardValues = {
        ["None"] = 0,
        ["Common"] = 100,
        ["Rare"] = 300,
        ["Epic"] = 1200,
        ["Champion"] = 3000,
    }
    local topChampion = nil
    local topFollower = nil
    local runeterranChampions = {}
    local allCards = {}
    
    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"
                if regionName == "Runeterra" then
                	table.insert(runeterranChampions,cardData.name)
                end
            else
                cardType = "Follower"
            end
        end
        
        table.insert(allCards,{code=cardCode, count=count, regionName=regionName})
        regions[regionName] = (regions[regionName] or 0) + count
        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
            elseif cardType == "Equipment" then
            	equipmentCount = equipmentCount + count
            end
            
        else
            table.insert(
                deckList,
                {
                    code = cardCode,
                    name = cardData.name,
                    count = count,
                    region = regionName,
                    type = cardType
                }
            )
        end
    end
    
    for i = 3, 1, -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
	
	--For cards with over 3 copies
	while true do
		local count = tonumber(VarInt.Pop(bytes)) or nil
		local set = VarInt.Pop(bytes)
		local region = VarInt.Pop(bytes)
		local card = VarInt.Pop(bytes)
		
		if not count or not set or not region or not card then
			break --end of stream
		end
		
		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], count, cardCode, regionData.name)
	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 .. '" does not exist'
        end
        
        handleCardData(lorData[cardCode], fourPlusCount, cardCode, regionData.name)
    end
    
    local multiregion = {}
    if args['detail'] then
    	for _, champion in ipairs(runeterranChampions) do 
    		for _, data in ipairs(allCards) do
    			if p.isFromRuneterran{data.code,champion} then
    				regions["Runeterra"] = (regions["Runeterra"] or 0) + data.count
    				regions[data.regionName] = (regions[data.regionName] or 0) - data.count
    				data.regionName = "Runeterra"
    			end
    		end
    	end
    	for _, data in ipairs(allCards) do
    		if data.regionName ~= "Runeterra" and lorData[data.code].categoryRefs and lorData[data.code].regions then
    			for _,ref in ipairs(lorData[data.code].categoryRefs) do
    				if ref == "Multi-region" then
	    				regions[data.regionName] = (regions[data.regionName] or 0) - data.count
						local region1 = data.regionName
						local region2 = nil
						for _,region in ipairs(lorData[data.code].regions) do
							if region1 ~= region then
								region2 = region
								break	
							end
						end
						table.insert(multiregion,{
							count=data.count,
							region1=region1,
							region2=region2
						})
						break
					end
    			end
			end
    	end
    	for _,data in pairs(multiregion) do
    		if not data.region2 or (regions[data.region1] and regions[data.region1] > 0) then
    			regions[data.region1] = (regions[data.region1] or 0) + data.count
    		elseif regions[data.region2] and regions[data.region2] > 0 then
    			regions[data.region2] = (regions[data.region2] or 0) + data.count
    		else
    			regions[data.region1] = (regions[data.region1] or 0) + data.count
    		end
    	end
    	for region, count in pairs(regions) do
    		if count <= 0 then
    			regions[region] = nil	
    		end
    	end
        return {
            shards = totalShards,
            topUnitCode = (topChampion or topFollower).code,
            regions = regions,
            champions = champions,
            followerCount = followerCount,
            spellCount = spellCount,
            landmarkCount = landmarkCount,
            equipmentCount = equipmentCount,
            rarities = rarities
        }
    else
    	for _, champion in ipairs(runeterranChampions) do
    		for _, data in ipairs(deckList) do
    			if p.isFromRuneterran{data.code,champion} then
    				data.region = "Runeterra"
    			end
    		end
    	end
    	for _, data in ipairs(allCards) do
    		if lorData[data.code].categoryRefs and lorData[data.code].regions then
    			for _,ref in ipairs(lorData[data.code].categoryRefs) do
    				if ref == "Multi-region" then
	    				regions[data.regionName] = (regions[data.regionName] or 0) - data.count
						break
					end
    			end
			end
    	end
    	for _, data in ipairs(deckList) do
			if data.region ~= "Runeterra" and lorData[data.code].categoryRefs and lorData[data.code].regions then
    			for _,ref in ipairs(lorData[data.code].categoryRefs) do
    				if ref == "Multi-region" then
    					local region1 = data.region
						local region2 = nil
						for _,region in ipairs(lorData[data.code].regions) do
							if region1 ~= region then
								region2 = region
								break	
							end
						end
						if not region2 or (regions[region1] and regions[region1] > 0) then
    						data.region = region1
						elseif regions[region2] and regions[region2] > 0 then
		    				data.region = region2
			    		else
			    			data.region = region1
			    		end
    					break
    				end
    			end
    		end
		end
        return deckList
    end
end

function p.isFromRuneterran(frame)
	local args = lib.frameArguments(frame)
	local lorData = require("Module:LoRData/data")
	local cardCode = args['code'] or args[1] or nil
	local region = args['region'] or args['champion'] or args[2] or nil
	
	if not cardCode or not region or not lorData[cardCode] then
		return false	
	end
	local card = lorData[cardCode]
	if card.supertype == "Champion" then
		return false
	end
	
	if region == "Aatrox" and card.subtype then
		for _, subtype in ipairs(card.subtype) do
			if subtype == "Darkin" then
				return true	
			end
		end
	elseif region == "Bard" then
		local CHIME_CARDS = { ["06BC011"] = true, ["06BC026"] = true, ["06BC031"] = true,
			["06BC032"] = true, ["06BC044"] = true, ["06MT029"] = true, ["06MT047"] = true,
			["08BC005"] = true, ["08SI021"] = true
		}
		if CHIME_CARDS[cardCode] then return true end
	elseif region == "Elder Dragon" then
		if card.cost >= 6 then return true end 
	elseif region == "Evelynn" and card.categoryRefs then
		for _, ref in ipairs(card.categoryRefs) do
			if ref == "Husk-generating" then
				return true	
			end
		end
	elseif region == "Jax" and card.subtype then
		for _, subtype in ipairs(card.subtype) do
			if subtype == "Weaponmaster" then
				return true	
			end
		end
	elseif region == "Jhin" and card.keywordRefs then
		for _, ref in ipairs(card.keywordRefs) do
			if ref == "Skill-generating" then
				return true	
			end
		end
	elseif (region == "Kayn" or region == "Varus") and card.subtype then
		for _, subtype in ipairs(card.subtype) do
			if subtype == "Cultist" then
				return true	
			end
		end
	elseif region == "Neeko" and card.subtype then
		local SUBTYPES = { ["Bird"] = true, ["Cat"] = true, ["Dog"] = true,
			["Elnuk"] = true, ["Fae"] = true, ["Reptile"] = true, ["Spider"] = true
		}
		for _, subtype in ipairs(card.subtype) do
			if SUBTYPES[subtype] then
				return true	
			end
		end
	elseif region == "Ryze" and card.type == "Spell" and card.keywords then
		for _, keyword in ipairs(card.keywords) do
			if keyword == "Burst" or keyword == "Focus" then
				return true	
			end
		end
	elseif region == "The Poro King" then
		local PORO_CARDS = { ["01FR016"] = true, ["01FR025"] = true, ["02FR003"] = true,
			["03FR018"] = true, ["03PZ018"] = true, ["06BC043"] = true, ["09FR009"] = true,
			["99FR001"] = true, ["99FR002"] = true, ["99PZ118"] = true
		}
		if PORO_CARDS[cardCode] then return true end
		if card.subtype then
			for _, subtype in ipairs(card.subtype) do
				if subtype == "Poro" then
					return true	
				end
			end
		end
	end
	
	return false
end

function p.adventureRegions(frame)
	local args = lib.frameArguments(frame)
	local str = args[1] or nil
	local size = args["size"] or args[2] or "20px"
	local sep = args["sep"] or "&nbsp;&nbsp;"
	
	if not str then return nil end  -- Exit if no input
    
    local result = {}
    local REGION_CODES = {
	    DE = "Demacia",
	    FR = "Freljord",
	    IO = "Ionia",
	    NX = "Noxus",
	    PZ = "Piltover and Zaun",
	    SI = "Shadow Isles",
	    BW = "Bilgewater",
	    SH = "Shurima",
	    MT = "Targon",
	    BC = "Bandle City",
	    RU = "Runeterra"
	}
    
    for i = 1, #str, 2 do
        local pair = str:sub(i, i + 1):upper()
        mw.log(pair)
        local region = REGION_CODES[pair] or "Neutral"
        table.insert(result, "[[File: " .. region .. " LoR Region.png|" .. size .. "|link=]]")
    end
    
    return table.concat(result, sep)
end

function p.adventureRewards(frame)
	local args = lib.frameArguments(frame)
	local step = args["step"] or "5"
	step = tonumber(step)
	if not step then
		return "Step Error."
	end
	
	local base_start = "<center><table style=\"border-collapse: collapse;\"><tr>"
	local base_end = "</tr></table></center>"
	local r = ""
	local count = 0
	
	for i, a in ipairs(args) do
		count = i
	end
	
	local rows = math.ceil(count/step)
	local index_lastrow = ((rows-1)*step)+1
	
	for i, a in ipairs(args) do
		if i % step == 1 then
			if i ~= 1 then
				r = r .. base_end
			end
			r = r .. base_start
			if i < index_lastrow then
				r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452; border-left: 1px solid #3e4452; border-bottom: 1px solid #3e4452;\"> "
			else
				r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452; border-left: 1px solid #3e4452;\"> "	
			end
		else
			if i < index_lastrow then
				r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452; border-bottom: 1px solid #3e4452;\"> "
			else
				r = r .. "<td style=\"text-align: center; padding: 5px; border-right: 1px solid #3e4452;\"> "	
			end
		end
		r = r .. args[i] .. "</td>"
	end
	r = r .. base_end
	return frame:preprocess(r)
end

function p.headerDeck(frame)
    local args = lib.frameArguments(frame)
    
    local builder = require("Module:SimpleHTMLBuilder")
    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 equipmentCount = deckDetail['equipmentCount'] or nil
    local rarities      = deckDetail['rarities'] or nil
    
    local template = builder.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 = builder.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=LoR:' .. regionName .. ']]')
                :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=LoR:Shard]]' .. Utility.comma_value(totalShards))
            :done()
    deckHeader:done()
    
    local deckInfo = builder.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', '240px')
            :css('left', '15px')
            :css('font-size', '20px')
            :css('color', '#FFE3B0')
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Follower card.png|25px|link=LoR:Follower]]' .. followerCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Spell card.png|25px|link=LoR:Spell]]' .. spellCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Keyword Landmark.png|25px|link=LoR:Landmark]]' .. landmarkCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Keyword Equipment.png|25px|link=LoR:Equipment]]' .. equipmentCount .. '&nbsp;')
            :done()
            :tag('br')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:LoR Common icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[1] or 0) .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:LoR Rare icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[2] or 0) .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:LoR Epic icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[3] or 0) .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:LoR Champion icon.png|25px|link=LoR:Card_types#By_rarity]]' .. (rarities[4] or 0) .. '&nbsp;')
            :done()
        :done()
    
    if #champions then
        local deckChampions = builder.create('div')
            :addClass('deck-champions')
            :css('position', 'absolute')
            :css('bottom', '20px')
            :css('left', '200px')
            :css('font-size', '20px')
            :css('color', '#FFE3B0')
                :tag('span')
                    :addClass('inline-image')
                    :wikitext('[[File:Champion card.png|25px|link=LoR:Champion]]')
                :done()
        for _, deckData in pairs(champions) do
            deckChampions
                :tag('span')
                    :addClass('inline-image')
                    :wikitext('[[File:' .. deckData.code .. '.png|60px|link=LoR:'.. deckData.code .. ']]')
                :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 = lib.frameArguments(frame)
    
    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 header = args['header'] or true
    if header == 'false' then
    	header = false
    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 = deckData.type .. '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 = ""
    if header then
    	template = template .. p.headerDeck{code = args['code'], name = args['name'], description = args['description']}
    end
    
    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.pocHeaderDeck(frame)
    local args = lib.frameArguments(frame)
    
    local builder = require("Module:SimpleHTMLBuilder")
    local adventureData = require("Module:LoRPoCData/adventure")
    local lorData = require("Module:LoRData/data")
    args['name'] = args['name'] or args['foe'] or 'Untitled Deck'
    local cmp = args['campaign'] or "World Adventures"
    
    if args['adventure'] == nil or not adventureData[cmp][args['adventure']] then
    	return 'Adventure missing or invalid'
    end
    local adventure = adventureData[cmp][args['adventure']]
    if args['foe'] == nil or not adventure[args['foe']] then
    	return 'Foe missing or invalid'
    end
    local foe = adventure[args['foe']]
    local stars = adventure.stars or false
    local adventureType = adventure["type"] or false
    
    args['code'] = args['code'] or foe.deck or nil
    if args['code'] == nil then
        return 'Empty Code'
    end
    
    local hasPower = foe.power or false
    local hasPowerArgs = args['powername'] or args['powerdesc'] or false
    local powername = nil
    local powerdesc = nil
    local powericon = nil
    local powerrarity = nil
    if hasPower then
    	powername = foe.power.name or 'Power Name'
	    powerdesc = foe.power.desc or 'Power Description.'
    	powericon = foe.power.icon or nil
    	powerrarity = foe.power.rarity or 'Common'
    end
    if hasPowerArgs then
    	powername = args['powername'] or powername or 'Power Name'
	    powerdesc = args['powerdesc'] or powerdesc or 'Power Description.'
    	powericon = args['powericon'] or powericon or nil
    	powerrarity = args['powerrarity'] or powerrarity or 'Common'
    	hasPower = true
    end
    
    local deckDetail = p.deckDataFromCode{code = args['code'], detail = true}
    if type(deckDetail) ~= 'table' then
        return deckDetail
    end
    local deckSimple = p.deckDataFromCode{code = args['code'], detail = false}
    if type(deckSimple) ~= 'table' then
        return deckSimple
    end
    
    local topUnitCode   = args['topUnitCode'] or foe.code or 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 equipmentCount = deckDetail['equipmentCount'] or nil
    local MAX_MOBILE_ITEMS = 3
    local MAX_ITEMS = 7
    
    --Extract items on cards
    local items = foe.items or nil
    
    --Fuse champions with items
    local championsItem = {}
    if items then
	    for code, data in pairs(items) do
	    	if lorData[code] and lorData[code]["supertype"] == "Champion" then
	    			table.insert(championsItem,{
	    				code=code,
	    				count=0,
	    				items=data
	    			})
	    	end
	    end
	end
    for _, deckData in pairs(champions) do
    	local found = false
    	for _, data in pairs(championsItem) do
    		if data.code == deckData.code then
    			found = true
    			data.count=deckData.count
    			break
    		end
    	end
    	if not found then
    		table.insert(championsItem, {
    			code=deckData.code,
    			count=deckData.count,
    			items=nil
    		})	
    	end
    end
    table.sort(championsItem, function(a, b)
	    if (lorData[a.code].name == args['foe']) ~= (lorData[b.code].name == args['foe']) then -- 1. Priority if "name" is the foe
	        return lorData[a.code].name == args['foe']
	    end
	    
	    local aitems = {}
	    local bitems = {}
	    if a.items then aitems = a.items end
	    if b.items then bitems = b.items end
	    
	    if #aitems ~= #bitems then -- 2. Sort by the number of items (higher count first)
	        return #aitems > #bitems
	    end
	    if lorData[a.code].cost ~= lorData[b.code].cost then -- 3. Sort by "cost" (lowest to highest)
	        return lorData[a.code].cost < lorData[b.code].cost
	    end
		return lorData[a.code].name < lorData[b.code].name -- 4. Sort alphabetically by "name"
    end)
    
    --detailed cards with items list
    local detailedItems = {}
	if items then
		for code, data in pairs(items) do
			table.insert(detailedItems, {
				code=code,
				name=lorData[code].name,
				cost=lorData[code].cost,
				items=data
			})	
		end
	end
	table.sort(detailedItems, function(a, b)
	    if (a.name == args['foe']) ~= (b.name == args['foe']) then -- 1. Priority if "name" is the foe
	        return a.name == args['foe']
	    end
	    if #a.items ~= #b.items then -- 2. Sort by the number of items (higher count first)
	        return #a.items > #b.items
	    end
	    if a.cost ~= b.cost then -- 3. Sort by "cost" (lowest to highest)
	        return a.cost < b.cost
	    end
		return a.name < b.name -- 4. Sort alphabetically by "name"
    end)
	
    local template = builder.create('div')
    	:css('display', 'inline-block')
        :css('position', 'relative')
        :css('font-family', 'BeaufortLoL')
        :tag('div')
            :css('opacity', '0.5')
            :wikitext('[[File:' .. topUnitCode .. '-full.png|800px|link=]]')
            :done()
            
    local deckHeader = builder.create('div')
            :addClass('deck-header')
            :css('position', 'absolute')
            :css('max-width', '650px')
            :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()
    
    local regionSize = 60
    local count = 0
    for _ in pairs(regions) do
        count = count + 1
        if count > 9 then
        	regionSize = 50
        	break
        end
    end
    for regionName, regionCount in pairs(regions) do
        deckHeader
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:' .. regionName .. ' LoR Region.png|' .. tostring(regionSize) .. 'px|link=LoR:' .. regionName .. ']]')
                :done()
    end
    
    if stars then
    	local starHeader= builder.create('div')
            :addClass('deck-header')
            :css('position', 'absolute')
            :css('top', '10px')
            :css('left', '730px')
            :tag('span')
                :css('font-weight', 'bold')
                :css('color', 'white')
                :css('font-size', '18px')
                	:tag('span')
                		:addClass('PoC-Star-Difficulty')
                		:css('white-space', 'nowrap')
                		:tag('span')
                			:css('position', 'relative')
                			:css('text-indent', '0')
                			:css('top', '-3px')
                			:wikitext('[[File:PoC Star icon.png|60px|link=]]')
                			:tag('span')
                				:css('position', 'absolute')
                				:css('left', '17px')
                				:css('top', '30%')
                				:css('user-select', 'none')
                				:css('line-height', '1.5')
                				:css('text-align', 'center')
                				:css('width', '25px')
                				:css('height', '25px')
                				:css('transform', 'translateY(-25%)')
                				:css('text-shadow', '0.5px 0.5px 1px #000, 0.5px 0.5px 1px #000, -0.5px -0.5px 1px #000, -0.5px -0.5px 1px #000, 0.5px -0.5px 1px #000,  0.5px -0.5px 1px #000, -0.5px 0.5px 1px #000, -0.5px 0.5px 1px #000, 0.5px 0.5px 1px #000, 0.5px 0.5px 1px #000;')
                				:wikitext(stars)
                			:done()
                		:done()
                	:done()
            :done()
            
        template
            :node(starHeader)
    end
    
    if adventureType then
    	local leftOffset = '660px'
    	if not stars then leftOffset = '730px' end
    	
    	local typeImage = ''
    	if adventureData.Icons[adventureType]  then
    		typeImage = '[[File:' .. adventureData.Icons[adventureType] .. '|60x60px|link=LoR:World Adventures]]'
    	end
    	
    	local typeHeader= builder.create('div')
            :addClass('deck-header')
            :css('position', 'absolute')
            :css('top', '8px')
            :css('left', leftOffset)
            :tag('span')
            	:wikitext(typeImage)
            :done()
            
        template
            :node(typeHeader)
    end
    
    if #championsItem > 0 then
        local deckChampions = builder.create('div')
            :addClass('deck-champions')
            :css('position', 'absolute')
            :css('top', '70px')
            :css('left', '20px')
            :css('font-size', '20px')
            :css('color', '#FFE3B0')
                :tag('span')
                    :addClass('inline-image')
                    :wikitext('[[File:Champion card.png|25px|link=LoR:Champion]]')
                :done()
        
        local n = 0
        for _, deckData in pairs(championsItem) do
        	if (n == MAX_MOBILE_ITEMS and args['mobile']) or (n == MAX_ITEMS) then
        		deckChampions
	                :tag('span')
		                :css('font-weight', 'bold')
		                :css('font-size', '30px')
	                    :addClass('inline-image')
	                    :wikitext(frame:preprocess('  ...'))
	                :done()
	             break
        	end
        	if deckData.items then
        		local s = '{{PoCCardItems|size=70|' .. deckData.code
        		for _, item in pairs(deckData.items) do
	        		s = s .. '|' .. item
	        	end
	        	s = s .. '}}'
        		deckChampions
	                :tag('span')
	                    :addClass('inline-image')
	                	:wikitext(frame:preprocess(s))
	                :done()
	                :tag('span')
	                    :addClass('inline-image')
	                    :wikitext(' x' .. deckData.count .. '&nbsp;')
	                :done()
	        else
	        	deckChampions
	                :tag('span')
	                    :addClass('inline-image')
	                    :wikitext('[[File:' .. deckData.code .. '.png|70px|link=LoR:'.. deckData.code .. ']]')
	                :done()
	                :tag('span')
	                    :addClass('inline-image')
	                    :wikitext('x' .. deckData.count .. '&nbsp;')
	                :done()
        	end
    		n = n+1
        end
        deckChampions:
            done()
        template
            :node(deckChampions)
    end
    
    if items then
        local deckItems = builder.create('div')
            :addClass('deck-champions')
            :css('position', 'absolute')
            :css('top', '175px')
            :css('left', '20px')
            :css('font-size', '20px')
            :css('color', '#FFE3B0')
                :tag('span')
                    :addClass('inline-image')
                    :wikitext('[[File:PoC Relic icon.png|25px]]')
                :done()
        
		local n = 0
        for _, itemList in ipairs(detailedItems) do
        	if (n == MAX_MOBILE_ITEMS and args['mobile']) or (n == MAX_ITEMS) then
        		deckItems
	                :tag('span')
		                :css('font-weight', 'bold')
		                :css('font-size', '30px')
	                    :addClass('inline-image')
	                    :wikitext(frame:preprocess('  ...'))
	                :done()
	             break
        	end
        	local count = 0;
        	local nonChampion = true
        	for _, data in pairs(championsItem) do
    			if data.code == itemList.code then
	    			nonChampion = false
	    			break
        		end
        	end
        	if nonChampion then
        		for _, data in pairs(deckSimple) do
	        		if data.code == itemList.code then
	        			count = data.count
	        			break
	        		end
	        	end
	        	local s = '{{PoCCardItems|size=70|' .. itemList.code
        		for _, item in ipairs(itemList.items) do
	        		s = s .. '|' .. item
	        	end
	        	s = s .. '}}'
	            deckItems
	                :tag('span')
	                    :addClass('inline-image')
	                    :wikitext(frame:preprocess(s))
	                :done()
	                :tag('span')
	                    :addClass('inline-image')
	                    :wikitext(' x' .. count .. '&nbsp;')
	                :done()
	        	n = n+1
	        end
        end
        deckItems:
            done()
        template
            :node(deckItems)
    end
    
    local powerImage
    local powerTitle
    local powerInfo
    if hasPower then
    	local image_leftOffset = 230
    	if args['mobile'] then image_leftOffset = 200 end
	    powerImage = builder.create('div')
	        :addClass('deck-info')
	        :css('position', 'absolute')
	        :css('top', '300px')
	        :css('left', tostring(image_leftOffset) .. 'px')
	    if powericon then
	    	powerImage
		    	:tag('span')
		            :css('display', 'inline-block')
		            :wikitext(frame:preprocess('{{PoCPowerIconOverlay|' .. powericon .. '|' .. powerrarity .. '|80}}'))
		        :done()
		end
	        powerImage
	        	:done()
	        
	    powerTitle = builder.create('div')
	        :addClass('deck-info')
	        :css('position', 'absolute')
	        :css('top', '300px')
	        :css('left', tostring(image_leftOffset+100) .. 'px')
	        :css('color', '#FFE3B0')
	        :css('font-weight', 'bold')
	        :tag('span')
	            :css('display', 'inline-block')
	            :css('font-size', '16px')
	            :wikitext(powername)
	        :done()
	        powerTitle:done()
	    
	    powerInfo = builder.create('div')
	        :addClass('deck-info')
	        :css('position', 'absolute')
	        :css('top', '330px')
	        :css('left', tostring(image_leftOffset+100) .. 'px')
	        :css('color', '#FFE3B0')
	        :tag('span')
	            :css('display', 'inline-block')
	            :css('max-width', '460px')
	            :css('font-size', '14px')
	            :wikitext(frame:preprocess(powerdesc))
	        :done()
	        powerInfo:done()
    end
    
    local health = foe.health or '?'
    local mana = foe.mana or '?'
    local hand = foe.hand or '?'
    local vicious = foe.vicious or false
    
    if hasPower then
    	template
	        :node(deckHeader)
	        :node(powerImage)
	        :node(powerTitle)
	        :node(powerInfo)
	else
		template
			:node(deckHeader)
    end

	local color = '#FFE3B0'
	if vicious then
		color = '#FF7474'
		if tonumber(health) then health = tostring(tonumber(health) + 10) end
		if tonumber(mana) then mana = tostring(tonumber(mana) + 1) end
		if tonumber(hand) then hand = tostring(tonumber(hand) + 1) end
	end
    
    template
        :tag('div')
            :addClass('deck-info')
            :css('position', 'absolute')
            :css('top', '320px')
            :css('left', '15px')
            :css('font-size', '20px')
            :css('color', '#FFE3B0')
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Follower card.png|25px|link=LoR:Follower]]' .. followerCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Spell card.png|25px|link=LoR:Spell]]' .. spellCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Keyword Landmark.png|25px|link=LoR:Landmark]]' .. landmarkCount .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Keyword Equipment.png|25px|link=LoR:Equipment]]' .. equipmentCount .. '&nbsp;')
            :done()
        :done()
        :tag('div')
        	:addClass('deck-info')
            :css('position', 'absolute')
            :css('top', '360px')
            :css('left', '15px')
            :css('font-size', '20px')
            :css('color', color)
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:PoC Health icon.png|25px|link=LoR:Nexus Health]]' .. health .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image') 
                :wikitext('[[File:LoR mana icon alt.png|25px|link=LoR:Terminology#Mana]]' .. mana .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext('[[File:Keyword Draw.png|25px|link=]]' .. hand .. '&nbsp;')
            :done()
            :tag('span')
                :addClass('inline-image')
                :wikitext(lib.ternary(vicious,frame:preprocess('  {{PoCPowerIconOverlay|04SI017-full.png|Special|30}}'),''))
            :done()
        :done()
    
    template:allDone()
    
    return tostring(template)
end

function p.pocDeckDecoder(frame)
    local args = lib.frameArguments(frame)
    local adventureData = require("Module:LoRPoCData/adventure")
    local cmp = args['campaign'] or "World Adventures"
    
    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
    
    if args['adventure'] == nil or not adventureData[cmp][args['adventure']] then
    	return 'Adventure missing or invalid'
    end
    local adventure = adventureData[cmp][args['adventure']]
    if args['foe'] == nil or not adventure[args['foe']] then
    	return 'Foe missing or invalid'
    end
    local foe = adventure[args['foe']]
    local items = foe.items or {}
    local deckItems = {}
    
    args['code'] = args['code'] or foe.deck or nil
    if args['code'] == nil then
        return 'Empty Code'
    end
    
    local header = args['header'] or true
    if header == 'false' then
    	header = false
    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 = deckData.type .. 's'
        else
            groupByKey = 'List'
        end
        
        if result[groupByKey] == nil then
            result[groupByKey] = {}
        end
        
        if items[deckData.code] then
        	table.insert(deckItems,deckData.code)
        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 = ""
    if header then
    	template = template .. p.pocHeaderDeck(frame)
    end
    
    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:''' <span style='word-wrap: break-word;'>" .. args['code'] .. '</span>' .. 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 = ""
        local isFirst = true
        for _, cardData in ipairs(regionData) do
            if cardData.type ~= type then
                if (type ~= "") then
                    template = template .. "|}" .. newline
                end
                template = template .. "{| class=\"wikitable\"" .. newline .. "! colspan=5 style=\"background-color: #374b65; text-align: left;\" |" .. cardData.type .. "s" .. newline .. "|-" .. newline
				isFirst = true
                type = cardData.type
            end
            
            local cardCodeTemplate = ""
            
			if isFirst then
				cardCodeTemplate = cardCodeTemplate .. '|-' .. newline
				isFirst = false
			else
				cardCodeTemplate = cardCodeTemplate .. '|' .. newline
				isFirst = true
			end
			
            cardCodeTemplate = cardCodeTemplate .."| {{LoR|" .. cardData.name
            if cardData.type ~= "Champion" then
                cardCodeTemplate = cardCodeTemplate .. "|code=" .. cardData.code
            end
            cardCodeTemplate = cardCodeTemplate .. "}}" .. " (x" .. cardData.count .. ")" .. newline
            
            
            cardCodeTemplate = cardCodeTemplate .. "| "
            if items[cardData.code] then
            	for _, item in ipairs(items[cardData.code])	do
            		if item:sub(1, 1) == "I" then
            			cardCodeTemplate = cardCodeTemplate .. "{{PoC|item|code=" .. item .. "}} "
            		else
            			cardCodeTemplate = cardCodeTemplate .. "{{PoC|relic|code=" .. item .. "}} "
            		end
            	end
            end
    		
            template = template .. cardCodeTemplate .. newline
        end
        template = template .. "|}" .. newline
    end
    
    local extraItems = ""
    local isFirst = true
    for key, itemList in pairs(items) do
    	if true then
    	end
    	if not Utility.table_contains(deckItems,key) then
    		if isFirst then
				extraItems = extraItems .. '|-' .. newline
				isFirst = false
			else
				extraItems = extraItems .. '|' .. newline
				isFirst = true
			end
        	extraItems = extraItems .. "| {{LoR|code=" .. key .. "}}" .. newline .. "| "
    		for _, item in ipairs(itemList)	do
        		if item:sub(1, 1) == "I" then
        			extraItems = extraItems .. "{{PoC|item|code=" .. item .. "}} "
        		else
        			extraItems = extraItems .. "{{PoC|relic|code=" .. item .. "}} "
        		end
    		end
    		extraItems = extraItems .. newline
    	end
    end
    
    if extraItems ~= "" then
    	if groupBy == 'region' or groupBy == 'type' then
    		template = template .. "|-|" .. newline ..  "Non-deck=" .. newline .. "'''Code:''' " .. args['code'] .. newline	
    	else
    		-- template = template .. ";Non-deck cards with items" .. newline
    	end
    	template = template .. "{| class=\"wikitable\"" .. newline .. "! colspan=5 style=\"background-color: #374b65; text-align: left;\" | Non-deck cards with items" .. newline .. "|-" .. newline .. extraItems .. "|}" .. newline
    end
    
    template = template .. "</tabber>"

    return frame:preprocess(template)
end

function p.championRoster(frame)
    local args = lib.frameArguments(frame)
    	
	local size = args["size"] or "120px"
	
    local lorData  = require('Module:LoRData/data')
    local lorTable = {}
    local s        = ""
    
    local REGION_CODES = {
	    DE = "Demacia",
	    FR = "Freljord",
	    IO = "Ionia",
	    NX = "Noxus",
	    PZ = "Piltover and Zaun",
	    SI = "Shadow Isles",
	    BW = "Bilgewater",
	    SH = "Shurima",
	    MT = "Targon",
	    BC = "Bandle City",
	    RU = "Runeterra"
	}
    
    local cardtable = {}
    for cardcode in pairs(lorData) do
        table.insert(cardtable, cardcode)
    end
    table.sort(cardtable)
    
    local championtable = {}
    
    for i, cardcode in pairs(cardtable) do
        repeat
            local t        = lorData[cardcode]
            if t.rarity ~= 'Champion' then
                break
            end
            
            t['code'] = cardcode
            table.insert(championtable, t)
            
            break
        until true
    end
    -- END
    table.sort(championtable, function(a, b) return a['name'] < b['name'] end)
    
    local rosterText = '' -- Temporarily will be class poc-roster
    rosterText = rosterText .. '<div class="poc-roster-container" style="display: flex; flex-direction: column; margin: 0 auto; padding: 5px; text-align: center;">'
    rosterText = rosterText .. '<div class="poc-roster" style="display: flex; justify-content: space-evenly; flex-wrap: wrap;">'
    
    for _, card in pairs(championtable) do
    	rosterText = rosterText .. '<div class="poc-roster-champion" style="padding:0.5em;" data-champion="' .. card.name .. '" data-searchname="' .. card.name .. '" data-game="lor"'
    	if card.regions then
    		rosterText = rosterText .. ' data-region="' .. table.concat(card.regions, ", ") .. '">'
    	else
    		rosterText = rosterText .. ' data-region="' .. REGION_CODES[card.code:sub(3,4):upper()] .. '">'
    	end
    	rosterText = rosterText .. '[[File:' .. card.code .. '.png|' .. size .. '|link=LoR:' .. card.name .. ']] <br> [[LoR:' .. card.name .. '|' .. card.name .. ']]</div>'
    end
    
    rosterText = rosterText .. '</div>'
    rosterText = rosterText .. '</div>'
    
    return rosterText
end

function p.championRosterPoC(frame)
    local args = lib.frameArguments(frame)
    	
	local size = args["size"] or "120px"
	
    local pocData  = require('Module:LoRPoCData/data')
    local lorTable = {}
    local s        = ""
    
    local rosterText = ''
    rosterText = rosterText .. '<div class="poc-roster-container" style="display: flex; flex-direction: column; margin: 0 auto; padding: 5px; text-align: center;">'
    rosterText = rosterText .. '<div class="poc-roster" style="display: flex; justify-content: space-evenly; flex-wrap: wrap;">'
    
    pocData = pocData["Champion"]
    local championtable = {}
    for champion, data in pairs(pocData) do
    	if (data["playable"]) then
        	table.insert(championtable, data)
        end
    end
    table.sort(championtable, function(a, b) return a.name < b.name end)
    
    for _, data in pairs(championtable) do
    	rosterText = rosterText .. '<div class="poc-roster-champion" style="padding:0.5em;" data-champion="' .. data.name .. '" data-searchname="' .. data.name .. '" '
    	rosterText = rosterText .. 'data-game="lor" data-playstyle="' .. (data.playable.style or "no") .. '" data-difficulty="' .. (data.playable.difficulty or "no") .. '" '
    	rosterText = rosterText .. 'data-region="' .. table.concat(data.playable.region, ", ") .. '" '
    	if data.playable.constellationRegion then
    		rosterText = rosterText ..'data-constellation="' .. data.playable.constellationRegion .. ', hasConstellation" '
    	else
    		rosterText = rosterText ..'data-constellation="constellation-none" '
    	end
    	rosterText = rosterText .. '>[[File:' .. data.id .. '.png|' .. size .. '|link=LoR:' .. data.name .. '/PoC]] <br> [[LoR:' .. data.name .. '/PoC|' .. data.name .. ']]</div>'
    end
    
    rosterText = rosterText .. '</div>'
    rosterText = rosterText .. '</div>'
    
    return rosterText
end

function p.getPoCChampionGrid(frame)
	-- Author: @Bladenite
	local args = lib.frameArguments(frame)
    	
	local size = args["size"] or "120px"
	local pocData  = require('Module:LoRPoCData/data')
	local gridText = ''
	
	-- Creating outer container
	gridText = gridText .. '<div class="poc-champions-flexbox-container" style="display: flex; flex-wrap: wrap; gap: 7.5px; justify-content: space-around;">'
	
	-- Getting playable champions data
	pocData = pocData["Champion"]
    local championtable = {}
    for champion, data in pairs(pocData) do
    	if (data["playable"]) then
        	table.insert(championtable, data)
        end
    end
    table.sort(championtable, function(a, b) return a.name < b.name end)
    
    for _, data in pairs(championtable) do
    	gridText = gridText .. '<div class="poc-champion-container" '
    	
    	-- Adding data for search parameters
    	gridText = gridText .. 'data-champion="' .. data.name .. '" data-searchname="' .. data.name .. '" '
    	gridText = gridText .. 'data-game="lor" data-playstyle="' .. (data.playable.style or "no") .. '" data-difficulty="' .. (data.playable.difficulty or "no") .. '" '
    	gridText = gridText .. 'data-region="' .. table.concat(data.playable.region, ", ") .. '" '
    	if data.playable.constellationRegion then
    		gridText = gridText ..'data-constellation="' .. data.playable.constellationRegion .. ', hasConstellation" '
    	else
    		gridText = gridText ..'data-constellation="constellation-none" '
    	end
    	gridText = gridText .. '>' --closing div
    	
    	-- Building champion board
    	gridText = gridText .. '<div class="poc-champion-circle"> [[File:' .. data.id .. '-circle.png|75px|link=LoR:' .. data.name .. '/PoC]]</div>'
    	gridText = gridText .. '<div class="poc-champion-name"> [[LoR:' .. data.name .. '/PoC|' .. data.name .. ']]</div>'
    	gridText = gridText .. '<div class="poc-champion-region">'
    	if #data.playable.region == 1 then
    		gridText = gridText .. '[[File:' .. data.playable.region[1] .. ' LoR Region.png|35px|link=]]</div>'
    	elseif #data.playable.region == 2 then
    		gridText = gridText .. '[[File:' .. data.playable.region[1] .. ' LoR Region.png|35px|link=]] '
    		gridText = gridText .. '[[File:' .. data.playable.region[2] .. ' LoR Region.png|35px|link=]]</div>'
    	end
    	if data.playable.style then
    		gridText = gridText .. '<div class="poc-champion-info"><span class="poc-' .. data.playable.style:lower() .. '">[[File:' .. data.playable.style .. ' LoR icon.png|20px|link=]] ' .. data.playable.style .. '</span> • '
    	end
    	if data.playable.difficulty then
    		gridText = gridText .. '<span class="poc-' .. data.playable.difficulty:lower() .. '"> ' .. data.playable.difficulty .. '</span></div>'
    	end
    	
    	gridText = gridText .. '<div class="poc-champion-power" style="display: inline-flex; flex-wrap: wrap; justify-content: center;'
    	if data.name ~= 'Aurelion Sol' then
    		gridText = gridText .. 'width: 120px;' -- Controls the number of powers in one line (120px -> 3 powers per line)
    	end
    	gridText = gridText .. '>'
    	
    	-- Ordering the powers correctly
    	for i, power in ipairs(data.playable.power) do
    		if power.code then
	    		gridText = gridText .. '<span class="skin-icon" style="white-space: nowrap; order: ' .. tostring(i) .. ';"'
	    		gridText = gridText .. ' data-game="lor" data-skin="PoC" data-champion="' .. power.code .. '"><span style="position:relative;text-indent:0;">'
	    		gridText = gridText .. '[[File:' .. power.code .. '.png|30px|link=LoR:' .. data.name .. '/PoC]]&#8202;&#8202;</span></span>'
    		end
    	end
    	gridText = gridText .. '</div>'
    	if data.support then
    		gridText = gridText .. '<div class="poc-champion-reinforcement"><span class="lor-tooltip" data-param="' .. data.id .. ',' .. data.support[1] .. ',' .. data.support[2] .. ', 50%">[[File:LoR Card icon.png|17px|link=|alt=Reinforcements]]</span></div>'
    	end
    	
    	-- Closing champion container
    	gridText = gridText .. '</div>'
    end
	-- Closing grid container
    gridText = gridText .. '</div>'
    
    return gridText
end

function p.pocChampionNavigation(frame)
	local args = lib.frameArguments(frame)
    	
	local size = args["size"] or "80px"
	local constellationOnly = args["constellationOnly"] or false
	
    local pocData  = require('Module:LoRPoCData/data')
    local lorTable = {}
    local s = ""
    local navText = ""
    
    pocData = pocData["Champion"]
    local championtable = {}
    for champion, data in pairs(pocData) do
    	if constellationOnly ~= false then
    		if (data["playable"]) and (data["playable"]["constellationRegion"]) then
        		table.insert(championtable, data)
        	end
    	else
    		if (data["playable"]) then
        		table.insert(championtable, data)
        	end
    	end
    end
    table.sort(championtable, function(a, b) return a.name < b.name end)
    
    for _, data in pairs(championtable) do
    	navText = navText .. '[[File:' .. data.id .. '.png|' .. size .. '|link=LoR:' .. data.name .. '/PoC]]'
    end
    
    return navText
end

function p.pocWeekliesDateRange()
	local today = os.date("*t")
	local current_wday = today.wday
	local days_to_add = (2 - current_wday) % 7  -- 2 corresponds to Monday
	
	if days_to_add == 0 then
	    days_to_add = 7
	end
	days_to_add = days_to_add - 1  -- Last day is non-inclusive
	
	local next_sunday_time = os.time(today) + days_to_add * 86400  -- 86400 seconds in a day
	local next_sunday = os.date("*t", next_sunday_time)
	local next_month_name = os.date("%B", next_sunday_time)
	
	local prev_monday_time = next_sunday_time - 6 * 86400
	local prev_monday = os.date("*t", prev_monday_time)
	local prev_month_name = os.date("%B", prev_monday_time)
	
	return string.format("%s %d – %s %d", prev_month_name, prev_monday.day, next_month_name, next_sunday.day)
end

function p.subtypeContent(frame)
    local args = lib.frameArguments(frame)
    
    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 = '[[LoR:' .. subtype .. '|' .. 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.regionContent(frame)
    local args = lib.frameArguments(frame)
    
    local cardcode = args['code'] or args[1] or nil
    if not cardcode then
    	return ''
    end
    
    local lorData   = mw.loadData('Module:LoRData/data')
    if not lorData[cardcode] then
    	return ''
	end
	
    local regions = {}
    if not lorData[cardcode].regions then
    	local regionCode = string.sub(cardcode, 3, 4)
    	for _, regionData in pairs(REGIONS) do
	    	if regionData.code == regionCode then
	    		table.insert(regions, regionData.name)
	    		break
	    	end
	    end
    else
    	regions = lorData[cardcode].regions
    end
    
    regiontext = ''
    for _, region in pairs(regions) do
    	regionlink = '[[File:' .. region .. ' LoR Region.png|20px]] '
    	regionlink = regionlink .. '[[LoR:' .. region .. '|' .. region .. ']]'
    	if regiontext ~= '' then
    		regiontext = regiontext .. ", " .. regionlink
    	else
    		regiontext = regiontext .. regionlink
    	end
    	
    	regiontext = regiontext .. "[[" .. "Category:LoR " .. region .. "]]"
    end
    
    if regiontext == '' then
    	regiontext = 'N/A'
    end
    
    return frame:preprocess(regiontext)
end

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

    if lorData[cardcode].alternate then
        artworkText = artworkText .. "<li style='display:inline-block;text-indent:0px;'>[[File:" .. cardcode
        	.. "-alt-full.png|thumb|" .. imageSize .. "|Censored|left]]"
    end
	
    for _, artwork in pairs(lorData[cardcode].artworks or {}) do
        local extension = 'png'
        local artworkParts = lib.split(artwork, "-")
        
        local HD            = false
	    local alternate     = false
	    local display       = false
	    local prerelease    = false
	    local old           = false
	    
	    for k, v in pairs(artworkParts) do
		  if (v == 'hd') then
		  	HD = true
		  end
		  if (v == 'alt') then
		  	alternate = true
		  end
		  if (v == 'display') then
		  	display = true
		  end
		  if (v == 'pre') then
		  	prerelease = true
		  end
		  if (v == 'old') then
		  	old = true
		  end
		end
        
        local artworkContents = {}
	
		if HD then
	    	table.insert(artworkContents, 'HD')
	    	extension = 'jpg'
	    end
	    if prerelease then
	    	table.insert(artworkContents, 'Pre-Release')
	    end
	    if alternate then
	    	table.insert(artworkContents, '[[LoR:List of cards/Alternate|Censored]]')
	    end
	    if display then
	    	table.insert(artworkContents, '[[League Displays]]')
	    	extension = 'jpg'
	    end
	    if old then
	    	table.insert(artworkContents, 'Old')
	    end
		
		if #artworkContents >= 1 then
			artworkContent = table.concat(artworkContents, " ")
		else
			artworkContent = 'Uncategorized'
		end

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

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

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

function p.artworkTooltip(frame)
    local args = lib.frameArguments(frame)
    
    local artworkCode = args['code'] or args[1] or nil
    
    if not artworkCode then
    	return ''
    end
    
    local artworkList = lib.split(artworkCode, ",")
    
    local width = 250
    if string.find(artworkList[1], '-full') then
		width = 300
    end

	if string.find(artworkList[#artworkList], '%%') then
		width = math.floor(width * (tonumber(string.match(artworkList[#artworkList], '%d+')/100) or 1))
		table.remove(artworkList, #artworkList)
    end
	
	if (#artworkList > 4) then
		width = width / 2
	end
	
	local artworkTooltip = '<div style="display:inline-flex; flex-wrap:wrap; width:' .. math.min(width * 4, width * #artworkList) .. 'px; box-shadow: rgb(1, 10, 19) 0px 0px 25px 15px;">'
    
    for k, v in pairs(artworkList) do
		artworkTooltip = artworkTooltip .. '<div style="display:inline-block; background-color: rgb(1, 10, 19); flex: 1 1;">'
	  	artworkTooltip = artworkTooltip .. '[[File:' .. v .. '.png|' .. width .. 'px]]'
	  	artworkTooltip = artworkTooltip .. '</div>'
	end
    
	artworkTooltip = artworkTooltip .. '</div>'
    
    return artworkTooltip
end

function p.randomCard(frame)
	local args = lib.frameArguments(frame)
		
    local lorData   = mw.loadData('Module:LoRData/data')
    
    local keys = {}
    for key, _ in pairs(lorData) do
        keys[#keys+1] = key --Store keys in another table
    end
    
    local randomCode
    -- Random
    math.randomseed(os.time())
	math.random(); math.random(); math.random()
	for x = 1,10 do
	    randomCode = keys[math.random(1, #keys)]
	end

    local randomCard = lorData[randomCode]
    local link = 'LoR:' .. randomCode
    
    local text = '[[File:' .. randomCode .. '.png|250px|link=' .. link .. ']]'
    text = text .. '[[' .. link .. '|' .. randomCard.name .. ']]'
    
    local cardMode = randomCard.mode
    if cardMode then
	    text = text .. ' ([[LoR:List of cards/' .. cardMode .. '|' .. cardMode .. ']])' 
	end
    
    return frame:preprocess(text)
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.LEFT_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]]