League of Legends Wiki
League of Legends Wiki

Данная группа модулей хранит информацию о картах Legends of Runeterra, а также функции по их обработке и выводу в основных статьях.

Перечень модулей:


From Модуль:LoRData/doc


--[[
    Содержит функции по обработке данных о картах Legends of Runeterra
    Авторы: en:User:Ninjamask, en:User:Emptylord, Участник:Mortorium
    Локализация: Mortorium
]]
--<pre>
local p         = {}
local lib       = require('Модуль:Feature')
local userError = require('Dev:User error')
local ERROR_CATEGORY  = "LuaError/LoRData"

--// Helper functions //
local function formatnil(text, ...)
    if select("#", ...) == 0 then return text end
    for i=1, select("#", ...) do
        if select(i, ...) == nil then return nil end
    end
    return mw.ustring.format(text, ...)
end

local function _isChampion(cardCode)
    return p.get{cardCode, "supertype"} == "Чемпион"
end

-- Определяет наличие навыка
local function _hasKeyword(card, keyword)
	return lib.has(card.keywords, keyword)
end

-- Определяет наличие взаимодействия с навыком
local function _hasKeywordRef(card, keywordRef)
	return lib.has(card.keywordRefs, keywordRef)
end

-- Определяет наличие взаимодействия с группой
local function _hasCategoryRef(card, categoryRef)
	return lib.has(card.categoryRefs, categoryRef)
end

-- Определяет принадлежность к группе
local function _isInGroup(card, group)
	return lib.has(card.subtype, group)
end

--[[
Фильтрует переданный список карт в соответствии переданным объектом фильтра
cardList - table - таблица карт
filter - Таблица параметров фильтра
  filter.set - номер набора
  filter.region - регион
  filter.type - тип карты
  filter.subtype - группа (подтип)
  filter.rarity - редкость
  filter.collectible - коллекционируемость
  filter.keyword - навык
  filter.desc - описание (регулярные выражения PCRE, разделитель - "%")
  filter.keywordref - связь с навыками (Keyword Reference)
  filter.categoryref - связь к группами (Category Reference)
  filter.special - "Мультирегиональность" | "Титанический"
  filter.expansion - дополнение
  filter.format - формат
  filter.hidden - ""
  filter.removed - удалена ли из игры
  filter.mana - стоимость (число, открытый или закрытый диапазон)
  filter.health - здоровье (число, открытый или закрытый диапазон)
  filter.power - сила атаки (число, открытый или закрытый диапазон)
]]
local function _filterCards(cardList, filter)
	-- Распарсить фильтр по мане, если есть такой
	local function _parseDigitValue(digit)
	    local minValue = 0
	    local maxValue = 1000
	    if(digit ~= nil) then
	    	if(string.match(digit, "%d+%-%d+")) then -- Формат вида "1-10"
	    		local range = lib.split(digit, "-")
	    		minValue = tonumber(range[1])
	    		maxValue = tonumber(range[2])
	    	elseif(string.match(digit, "%d+%+")) then -- Формат вида "7+"
	    		minValue = tonumber((mw.ustring.gsub(digit, "+", "")))
	    	elseif(string.match(digit, "%d+")) then -- Точное значение
	    		minValue = tonumber(digit)
	    		maxValue = tonumber(digit)
	    	end
	    end
	    return minValue, maxValue
	end
	-- s -> search - искомые значения
    local s_set       = tonumber(filter["set"]) or nil
    local s_region    = filter["region"] or nil
    local s_type      = filter["type"] or nil
    local s_subtype   = filter["subtype"] or nil
    local s_rarity    = filter["rarity"] or nil
    local s_collectible = lib.toBoolean(filter["collectible"])
    local s_desc      = filter["desc"] or nil
    local s_kw        = filter["keyword"] or nil     -- keyword
    local s_kwr       = filter["keywordref"] or nil  -- keywordRefs
    local s_catr      = filter["categoryref"] or nil -- categoryRefs
    local s_special   = filter["special"] or nil -- Особые атрибуты карт (мультирегиональность)
    local s_expansion = filter["expansion"] or nil
    local s_format    = filter["format"] or nil
    local s_hidden    = filter["hidden"] or nil -- учитывает только значения FILTER_TAGS
    local s_removed   = filter["removed"] or nil -- учитывает только значения FILTER_TAGS
    local s_mana      = filter["mana"] or nil
    local s_minMana, s_maxMana = _parseDigitValue(s_mana)
    local s_health    = filter["health"] or nil
    local s_minHealth, s_maxHealth = _parseDigitValue(s_health)
    local s_power     = filter["power"] or nil
    local s_minPower, s_maxPower = _parseDigitValue(s_power)
    
    local FILTER_TAGS = {
    	["include"] = true, -- Не игнорировать в итоге поиска
    	["only"] = true -- В итоге поиска показывать только их
    }
    
    local filteredResult = {}
    
    for cardId, cardData in pairs(cardList) do
        repeat
            local t         = cardData
            local region    = t.regions or { p.getRegionFromCardcode{cardId} }
            local set       = p.getSetFromCardcode{cardId}
            local rarity    = t.rarity
            local collectible = t.collectible
            local desc      = t.desc
            local keywords  = t.keywords
            local cost      = t.cost
            local health    = t.health
            local power     = t.power
            local expansion = t.expansion
            local hidden    = t.hidden
            local removed   = t.removed
            local formats   = t.formats
            
            -- short-cut eval
            if(FILTER_TAGS[s_hidden] ~= nil and (s_hidden == "only" and not(hidden))) then 
            	break
            else if(hidden) then break end
            end
            
            if(FILTER_TAGS[s_removed] ~= nil and (s_removed == "only" and not(removed))) then break
            elseif(removed) then break end
            
            -- Проверка региона
            if(s_region ~= nil and not(lib.has(region, s_region))) then break end
            
            -- Проверка набора
            if(s_set ~= nil and s_set ~= set) then break end
            
            if(s_rarity ~= nil and s_rarity ~= rarity) then break end
            
            if(s_collectible ~= nil and s_collectible ~= collectible) then break end
            
            if(s_desc ~= "*none*" and s_desc ~= nil) then
            	if(t.desc ~= nil) then
            		if(mw.ustring.find(t.desc, s_desc) == nil) then break end
            	else
            		break
            	end
            end
            
            if(s_expansion ~= nil and s_expansion ~= expansion) then break end
            
            if(s_formats ~= nil and not(lib.has(formats, s_format))) then break end
            
            -- Проверка типа
            if(s_type ~= nil) then
                if(s_type == "Сторонник" and t.type ~= "Боец" or t.supertype == "Чемпион") then break
                elseif(s_type == "Чемпион" and t.supertype ~= "Чемпион") then break
                elseif s_type ~= t.type then break end
            end
            
            -- Проверка группы
            if(s_subtype ~= nil and not(_isInGroup(t, s_subtype))) then break end
            
            -- Проверка навыка
            if(s_kw ~= nil and not(_hasKeyword(t, s_kw))) then break end
            
            -- Проверка взаимодействия с навыком
            if(s_kwr ~= nil and not(_hasKeywordRef(t, s_kwr))) then break end
            
            -- Проверка сложных категорий (мультирегиональность)
            if(s_cat ~= nil and s_cat == "Мультирегиональность") then break end
            
            -- Проверка взаимодействия с группой
            if(s_catr ~= nil and not(_hasCategoryRef(t, s_catr))) then break end
            
            if(cost < s_minMana or cost > s_maxMana) then break end
            if(health < s_minHealth or health > s_maxHealth) then break end
            if(power < s_minPower or power > s_maxPower) then break end
            
            if(s_special ~= nil) then
            	if(s_special == "Мультирегиональность" and #region ~= 0) then break end
            	if(s_special == "Титанический" and (t.health < 8 and t.power < 8)) then break end
            end
        	filteredResult[cardId] = cardData
        	break
        until(true)
    end
    return filteredResult
end

function p.get(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local PREPROCESSABLE_FIELDS = { ["desc"] = true, ["lvldesc"] = true, ["flavor"] = true }
 
    local get      = require ("Модуль:LoRData/getter")
    local cardcode = args["cardcode"] or args[1]
    local datatype = args["datatype"] or args[2]
    local output   = args["output"]   or args[3] or nil
    
    if(cardcode == nil) then error("No cardcode has been passed") end
    if(datatype == nil) then error("No datatype has been passed") end
    
    local setCode = p.getSetFromCardcode{ cardcode }
 
    local result = get[datatype](cardcode, setCode)
    
    if(output ~= nil and type(result) == "table") then
        if(output == "csv") then
            return table.concat(result, ",")
        elseif(output == "tiplor") then
        	local STANDARD_TEMPLATE = "Tip LoR"
        	local STANDARD_STYLE = "margin-right:1em;"
            table.sort(result)
            local expandedResult = {}
            for _, v in ipairs(result) do
            	table.insert(expandedResult, frame:expandTemplate{
            		title = STANDARD_TEMPLATE,
            		args = { v, style = STANDARD_STYLE }
            	})
            end
            return lib.tbl_concat{ ["tbl"] = expandedResult, ["sep"] = "false" }
        elseif(output == "template") then
            table.sort(result)
            local expandedResult = {}
            for _, v in ipairs(result) do
            	table.insert(expandedResult, frame:expandTemplate{
            		title = args["t_name"],
            		args = { v }
            	})
            end
            return lib.tbl_concat{ ["tbl"] = expandedResult, separator = args["separator"] }
        elseif(output == "custom") then 
            return frame:preprocess(lib.tbl_concat{
            	result, 
            	prepend = args["prepend"], 
            	append = args["append"], 
            	separator = args["separator"], 
            	index = args["index"]
            })
        end
    elseif(PREPROCESSABLE_FIELDS[datatype] and result ~= nil) then
        return frame:preprocess(result)
    elseif(result == nil) then
        return ""
    else
        return result
    end
end

-- Импортирует данные из игровых файлов в формате JSON в формат объектов Lua
-- В качестве источника данных использует заранее созданную подстраницу, например "Участник:Mortorium/LoR/Set 5/3.14.json"
function p.cardData(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	local json        = require("Модуль:JSON")
    local titleobject = mw.title.new(args[1])
    local o           = json.decode(titleobject:getContent())
    local result      = ""
    local cardData    = {}
    local lang        = mw.language.new( "ru" )
    local excludeOld  = args["excludeold"] or false
    local expansion   = args["expansion"] or nil
    
    local lorData     = p.collectLoRData()

	local RARITIES = {
		["нет"]       = "None",
		["обычное"]   = "Обычная",
		["редкое"]    = "Редкая",
		["эпическое"] = "Эпическая",
		["чемпион"]   = "Чемпион",
	}
	-- 4 and 8 tabs for lua table indentation
	local TAB  = "    "
	local DTAB = "        "
    
    for _, data in pairs(o) do
    	if(excludeOld) then
    		if(lorData[data.cardCode] == nil) then
    			table.insert(cardData, data)
    		end
    	else
        	table.insert(cardData, data)
        end
    end
    table.sort(cardData, function(a, b) return a.cardCode < b.cardCode end)
    
    -- ============================
    -- = Generate lua data output =
    -- ============================
    s = {[[
    -- <pre>
    -- Legends of Runeterra data
    
    return {
    ]]}
    
    for i, card in pairs(cardData) do
        -- StatTable data 
        local sLoop = {}
        
        -- После названий полей сделать равные отступы
        table.insert(sLoop, mw.ustring.format("%s[\"%s\"] = {", TAB, card.cardCode))
        table.insert(sLoop, mw.ustring.format("%s[\"name\"]         = \"%s\",", DTAB, card.name))
        table.insert(sLoop, mw.ustring.format("%s[\"type\"]         = \"%s\",", DTAB, card.type))
        table.insert(
        	sLoop, 
        	mw.ustring.format(
	        	"%s[\"rarity\"]       = \"%s\",",
	        	DTAB,
	        	lang:ucfirst(lang:lc(RARITIES[lang:lc(card.rarity)] or card.rarity))
	        )
	    )
	    if(card.subtypes[1] ~= nil) then
	    	table.insert(
	    		sLoop, 
	    		mw.ustring.format(
	    			"%s[\"subtype\"]      = {\"%s\"},",
	    			DTAB,
	    			lang:ucfirst(lang:lc(card.subtypes[1]))
	    		)
	    	)
	    end
	    if(card.supertype ~= "") then
	    	table.insert(
	    		sLoop, 
	    		mw.ustring.format(
	    			"%s[\"supertype\"]    = \"%s\",",
	    			DTAB,
	    			lang:ucfirst(lang:lc(card.supertype))
	    		)
	    	)
	    end
	    
	    local sKeywords = mw.ustring.format("{\"%s\"}", table.concat(card.keywords, "\", \""))
	    if(sKeywords ~= "{\"\"}") then
	    	table.insert(
	    		sLoop, 
	    		mw.ustring.format(
	    			"%s[\"keywords\"]     = %s,",
	    			DTAB,
	    			sKeywords
	    		)
	    	)
	    end
	    
	    table.insert(sLoop, mw.ustring.format("%s[\"collectible\"]  = %s,", DTAB, tostring(card.collectible)))
	    table.insert(sLoop, mw.ustring.format("%s[\"cost\"]         = %s,", DTAB, card.cost))
	    table.insert(sLoop, mw.ustring.format("%s[\"power\"]        = %s,", DTAB, card.attack))
	    table.insert(sLoop, mw.ustring.format("%s[\"health\"]       = %s,", DTAB, card.health))
	    if(card.descriptionRaw ~= "") then
	    	table.insert(
	    		sLoop,
	    		mw.ustring.format(
	    			"%s[\"desc\"]         = \"%s\",",
	    			DTAB, 
	    			string.gsub(string.gsub(string.gsub(card.descriptionRaw, '\"', '\\"'), "\r\n", "<br />"), "\n", "<br />")
	    		)
	    	)
	    end
	    if(card.levelupDescriptionRaw ~= "") then
	    	table.insert(
	    		sLoop,
	    		mw.ustring.format(
	    			"%s[\"lvldesc\"]      = \"%s\",",
	    			DTAB, 
	    			string.gsub(string.gsub(string.gsub(card.levelupDescriptionRaw, '\"', '\\"'), "\n", "<br />"), "\r\n", "<br />")
	    		)
	    	)
	    end
	    if(card.formats ~= nil) then
	    	local sFormats = mw.ustring.format("{\"%s\"}", table.concat(card.formats, "\", \""))
	    	table.insert(
	    		sLoop,
	    		mw.ustring.format(
	    			"%s[\"formats\"]      = %s,",
	    			DTAB,
	    			sFormats
	    		)
	    	)
	    end
	    if(card.regions[2] ~= nil) then
	    	local sRegions = mw.ustring.format("{\"%s\"}", table.concat(card.regions, "\", \""))
	    	table.insert(
	    		sLoop,
	    		mw.ustring.format(
	    			"%s[\"regions\"]      = %s,",
	    			DTAB,
	    			sRegions
	    		)
	    	)
	    end
	    
	    table.insert(
    		sLoop,
    		mw.ustring.format(
    			"%s[\"flavor\"]       = \"%s\",",
    			DTAB, 
    			string.gsub(string.gsub(string.gsub(card.flavorText, '\"', '\\"'), "\n", "<br />"), "\r\n", "<br />")
    		)
    	)
    	
    	table.insert(sLoop, mw.ustring.format("%s[\"artist\"]       = \"%s\",", DTAB, card.artistName))
    	
    	if(args["expansion"] ~= nil) then
    		table.insert(sLoop, mw.ustring.format("%s[\"expansion\"]    = \"%s\",", DTAB, args["expansion"]))
    	end
    	table.insert(sLoop, mw.ustring.format("%s},\n", TAB))
    	
    	table.insert(s, table.concat(sLoop, "\n"))
    end
    result = table.concat(s, "")
    result = result .. "}\n"
    result = result .. "-- </" .. "pre>\n" -- pre needs to be splitted with, because of Lua weirdness
    result = result .. "-- [[Category:Lua]]"
    
    return mw.text.tag{
    	["name"] = "pre",
    	["content"] = mw.text.nowiki(result)
    }
end

function p.getChampionRoster(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	
	local size    = args["size"] or "120px"
	local championCards = _filterCards(p.collectLoRData(), {rarity = "Чемпион", collectible = "true"})
	local champions = {}
	for cardCode, cardData in pairs(championCards) do table.insert(champions, {cardData.name, cardCode}) end
	table.sort(champions, function(a, b) return a[1] < b[1] end)
	
	local flexBlock = mw.html.create("div")
	flexBlock
		:attr("id", "LoRChampionRoster")
		:addClass("lor-portal-roster-flex")
		:newline()
		:done()
	
	local flexElement = mw.html.create("div")
	flexElement
		:addClass("lor-portal-roster-node")
		:done()
	
	for i, championTuple in ipairs(champions) do
		local name, code = championTuple[1], championTuple[2]
		local imageNode = mw.html.create("div")
		imageNode
			:addClass("lor-portal-roster-image")
			:wikitext(
				mw.ustring.format(
					"[[File:%s.png|%s|link=%s/LoR]]<br />[[%s/LoR|%s]]",
					code,
					size,
					name,
					name,
					name
				)
			)
			:done()
		flexElement:node(imageNode):newline():done()
	end
	
	flexBlock:node(flexElement):done()
    
    return tostring(flexBlock)
end

--[[
Создает таблицу карт, отфильтрованных по некоторому признаку
В статьях используется через Шаблон:Таблица карт LoR
]]
function p.getCardTable(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    	
    local lang      = mw.language.new("ru")
    local tableNode = mw.html.create('table')
    local format    = mw.ustring.format -- Алиас для форматирования вывода
    
    local REGION_LIST = mw.loadData("Модуль:LoRData/regions")
    local REGION_SHORTCUTS = mw.loadData("Модуль:LoRData/regionShortcuts")
    
    -- Значения фильтров для s_hidden и s_removed
    local FILTER_TAGS = {
    	["include"] = true,
    	["only"] = true
    }
    
    local RARITY_SORT = {
    	["None"] = 0,
    	["Обычная"] = 1,
    	["Редкая"] = 2,
    	["Эпическая"] = 3,
    	["Чемпион"] = 4,
    }
    
    tableNode
        :addClass('sortable article-table nopadding lor-cardlist')
        :css('width','100%')
        :css('text-align','center')
        :newline()
        :done()
    
    -- Составл заголовка зависит от учтенных фильтров (например при фильтре по региону столбец региона опускается)
    local headerNode = mw.html.create("tr")
    local headers = {}
    headerNode
    	:tag("th")
        :done()
        :newline()
        :tag('th')
            :wikitext('Имя')
        :done()
        :newline()
    
    if(not(s_type)) then
    	table.insert(headers, "type")
        headerNode
            :tag('th')
                :wikitext('Тип')
            :done()
            :newline()
    end
    if(not(s_subtype)) then
    	table.insert(headers, "subtype")
    	headerNode
            :tag('th')
                :wikitext('Группа')
            :done()
            :newline()
	end
	if(not(s_region)) then
    	table.insert(headers, "region")
    	headerNode
            :tag('th')
                :wikitext('Регион(ы)')
            :done()
            :newline()
	end
    
    headerNode
    	:tag('th')
            :css('width','20px')
            :tag('span')
                :attr('title','Мана')
                :wikitext('[[File:LoR mana icon.png|24px|link=|]]')
            :done()
            :done()
            :newline()
            :tag('th')
                :css('width','20px')
                :tag('span')
                    :attr('title','Сила')
                    :wikitext('[[File:LoR Power.png|24px|link=|]]')
                :done()
            :done()
            :newline()
            :tag('th')
                :css('width','20px')
                :tag('span')
                    :attr('title','Здоровье')
                    :wikitext('[[File:LoR Health.png|24px|link=|]]')
                :done()
            :done()
            :newline()
    if(not(s_rarity)) then
    	headerNode
            :tag('th')
                :wikitext('Редкость')
            :done()
        :done()
        :newline()
    end
	if(not(s_format)) then
    	table.insert(headers, "format")
    	headerNode
            :tag('th')
                :wikitext('Формат')
            :done()
            :newline()
	end
    
    tableNode:node(headerNode)
        
    -- START
    local filteredCards = _filterCards(p.collectLoRData(), args)
    local cardSequence = {}
    for cardId in pairs(filteredCards) do
        table.insert(cardSequence, cardId)
    end
    table.sort(cardSequence)
    
    local hasAnyCard = #cardSequence > 0
    
	for i, cardId in ipairs(cardSequence) do
		local t         = filteredCards[cardId]
        local region    = t.regions or { p.getRegionFromCardcode{cardId} }
        local set       = p.getSetFromCardcode{cardId}
        local rarity    = t.rarity
        local collectible = t.collectible
        local keywords  = t.keywords
        local cost      = t.cost
        local expansion = t.expansion
        local hidden    = t.hidden
        local removed   = t.removed
        local formats   = t.formats
        
        local cardnode  = mw.html.create('tr')
        local CARD_IMAGE = format(
        	"[[File:%s.png|75px|link=%s (Legends of Runeterra)]]<br />%s",
        	cardId,
        	t.name,
        	cardId
        )
        local CARD_LINK = format("[[%s (Legends of Runeterra)|%s]]", t.name, t.name)
        local CARD_RARITY = lib.ternary(
        	t.rarity == "None",
        	"",
        	format("[[File:%s rarity.svg|64px|link=]]&nbsp;", rarity)
        )
        -- Изображение
        cardnode
            :tag("td")
                :addClass("lor-card-icon")
                :attr("data-sort-value", cardId)
                :attr("data-param", cardId)
                :wikitext(CARD_IMAGE)
            :done()
        
        -- Название и ссылка
        cardnode
            :tag("td")
            	:addClass("lor-cardlist__cell--name")
                :wikitext(CARD_LINK)
            :done()
        
        -- Type
        if(not(s_type)) then
            cardnode
                :tag("td")
            		:addClass("lor-cardlist__cell--type")
                    :wikitext(t.type)
                :done()
        end
        
        -- Subtype
        if(not(s_subtype)) then
        	if(t.subtype ~= nil) then
            	-- Обязательно клонируется
            	local t_subtype = lib.cloneTable(t.subtype)
            	cardnode
	                :tag("td")
            			:addClass("lor-cardlist__cell--subtype")
	                    :wikitext(table.concat(t_subtype, "<br />"))
	                :done()
            else
            	cardnode
	                :tag("td")
            			:addClass("lor-cardlist__cell--subtype")
	                    :wikitext("&nbsp;")
	                :done()
	        end
        end
        
        -- Регион
        if(not(s_region)) then
        	local regionTable = {}
        	for i, v in ipairs(region) do
        		local regionTip = format(
        			"[[File:%s LoR Region.png|20px|link=%s (Legends of Runeterra)]]&nbsp;[[%s (Legends of Runeterra)|%s]]",
            		REGION_LIST[v],
            		v,
            		v,
            		REGION_SHORTCUTS[v] or v)
            	table.insert(regionTable, regionTip)
        	end
        	cardnode
                :tag("td")
            		:addClass("lor-cardlist__cell--region")
                    :attr("data-sort-value", region[1])
                    :wikitext(table.concat(regionTable, "<br />"))
                    :done()
                :done()
        end
        
        -- Стоимость
        cardnode
            :tag("td")
            	:addClass("lor-cardlist__cell--cost")
                :wikitext(t.cost)
            :done()
        
        -- Сила атаки
        cardnode
            :tag("td")
            	:addClass("lor-cardlist__cell--power")
                :wikitext(t.power)
            :done()
        
        -- Здоровье
        cardnode
            :tag("td")
            	:addClass("lor-cardlist__cell--health")
                :wikitext(t.health)
            :done()
        
        -- Rarity
        if(not(s_rarity)) then
            cardnode
                :tag("td")
            		:addClass("lor-cardlist__cell--rarity")
                	:attr("data-sort-value", tostring(RARITY_SORT[t.rarity]))
                    :wikitext(CARD_RARITY)
                :done()
        end
    
    	-- Формат
        if(not(s_format)) then
        	-- Обязательно клонируется
        	local t_formats = {}
        	if(t.formats ~= nil) then
        		t_formats = lib.cloneTable(t.formats)
        	end
            cardnode
                :tag("td")
            		:addClass("lor-cardlist__cell--format")
                    :wikitext(table.concat(t_formats, "<br />"))
                :done()
        end
        -- Add card row to the table
        tableNode
            :newline()
            :node(cardnode)
	end
    -- END
    if(not(hasAnyCard)) then
    	tableNode
    		:tag('tr')
    			:tag('td')
                	:addClass("lor-cardlist__cell--false")
    				:attr("colspan", #headers + 6)
    				:wikitext("По указанным критериям карт не найдено")
    				:done()
    			:done()
    		:allDone()
    	return tostring(tableNode)
    end
    
    tableNode:allDone()
    return tostring(tableNode)
end

function p.findCardByName(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
        
    local lorData     = p.collectLoRData()
    local search      = args['search'] or args[1]
    local entire      = args['entire'] or false
    
    local cardtable   = {}
    local resulttable = {}

    for cardcode in pairs(lorData) do
        table.insert(cardtable, cardcode)
    end
    table.sort(cardtable)
    
    for _, cardcode in ipairs(cardtable) do
        if entire == false and p.getSetFromCardcode{cardcode=cardcode} == 0 then
            -- Set 0 (Tutorial) is only taken when there is an "entire" param set
        else 
            if search == lorData[cardcode].name and not(lorData[cardcode].hidden) then
            	table.insert(resulttable, cardcode)
            end
        end
    end
    
    if #resulttable == 0 then
        return "Карта с именем " .. search .. " не найдена в Модуль:LoRData/data."
    elseif #resulttable == 1 then
        return resulttable[1]
    else
        return table.concat(resulttable, ",")
    end
end

function p.cardExists(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local lorData = p.collectLoRData()
    local searchName = args['card'] or args[1] 
    for k, v in pairs(lorData) do
        if v.name == searchName then return true end
    end
    
    return false
end

function p.getRegion(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local REGION_LIST = mw.loadData("Модуль:LoRData/regions")
    return REGION_LIST[args['region'] or args[1]]
end

function p.getRegionFromCardcode(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    local cardcode = args['cardcode'] or args[1] or nil
    local REGION_LIST = mw.loadData("Модуль:LoRData/regions")
    
    return REGION_LIST[mw.ustring.sub(cardcode, 3, 4)]
end

function p.getSetFromCardcode(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    local cardcode = args['cardcode'] or args[1] or nil
    if(cardcode == nil) then error("Incorrect card ID") end
    return tonumber(mw.ustring.sub(cardcode, 0, 2))
end

function p.organizeSubtypes(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	local cardCode = args['cardcode'] or args[1]
	local subtypes = p.get{cardCode, "subtype"}
	local categories = ""
	local links = ""
	local result = ""
	
	if(type(subtypes) == "table") then
		local subtypeLinks = {}
		for i, v in ipairs(subtypes) do
			table.insert(subtypeLinks, mw.ustring.format("[[%s (Legends of Runeterra)|%s]]", v, v))
			categories = categories .. mw.ustring.format("[[Категория:LoR %s]]", v)
		end
		result = table.concat(subtypeLinks, ", ") .. categories
	end
	return result
end

function p.formatRegions(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	
	local cardCode = args[1] or args["code"]
	local separator = args[2] or args["separator"] or ", "
	
	local lorData = p.collectLoRData()
	
	if(lorData[cardCode] == nil) then
		return userError("Код " .. cardCode .. " не распознан в Модуль:LoRData/data", "LuaError/LoRData")
	end
	
	local regionList = p.get{cardCode, "regions"}
	local result = ""
	if(regionList == "") then
		result = frame:expandTemplate{title = "Tip LoR", args = {p.getRegionFromCardcode{cardCode}}}
	else
		local templatesList = {}
		for i, v in ipairs(regionList) do
			mw.log(v)
			table.insert(templatesList, tostring(frame:expandTemplate{title = "Tip LoR", args = {v}}))
		end
		result = lib.tbl_concat{["tbl"] = templatesList, ["sep"] = separator}
	end
	return result
end

-- Создает форматированную строку о выпуске карты
-- Используется в инфобоксе
function p.formatSet(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	
	local cardCode = args[1] or args["code"]
	local setCode = string.sub(cardCode, 1, 2)
	local separator = args[2] or args["separator"] or ", "
	
	local lorData = p.collectLoRData()
	local setList = mw.loadData("Модуль:LoRData/sets")
	
	if(lorData[cardCode] == nil) then
		return userError(
			mw.ustring.format(
				"Код %s не распознан в Модуль:LoRData/data/set%s",
				cardCode,
				setCode
			),
			"LuaError/LoRData"
		)
	end
	
	local set = tostring(p.getSetFromCardcode{cardCode})
	local expansion = lorData[cardCode].expansion
	
	local setName = setList[set]
	if(not(setName)) then
		return userError("Выпуск " .. set .. " не распознан в Модуль:LoRData/sets", "LuaError/LoRData")
	end
	
	local setImageNumber = set
	local setLink = mw.ustring.format(
		"[[%s (Legends of Runeterra, сезон)|%s]]",
		setList[set],
		setList[set]
	)
	
	-- Для 6 выпуска нужно проверить подвыпуск Сага о даркинах
	if(set == "6" and expansion) then
		-- Если содержит в себе упоминание Саги о даркинах
		if(mw.ustring.find(expansion, "Сага о даркинах")) then
			setImageNumber = "6cde"
		end
	end
	
	local setImage = mw.ustring.format("[[Файл:LoR Set %s icon.png|20px]]", setImageNumber)
	
	local result = setImage .. " " .. setLink
	-- Если название дополнения совпадает с именем выпуска - не повторяться
	if(expansion and expansion ~= setName) then
		local expansionLink = mw.ustring.format(
			"[[%s (Legends of Runeterra)|%s]]",
			expansion,
			expansion
			)
		result = result .. " — " .. expansionLink
	end
	
	return result
end

function p.link(frame)
    local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
    
    local cardCode  = args["code"]
    
    -- Проверка корректности имени/кода
    if(cardCode == nil and args["name"]) then
		cardCode = p.findCardByName{search=args["name"], entire=args["entire"]}
		if(mw.ustring.find(cardCode, "Карта ") ~= nil) then
			return userError(
				mw.ustring.format(
					"Карта %s не найдена в Модуль:LoRData/data/setX",
					args["name"]
				),
				"LuaError/LoRData"
		)
		end
    end
    
    local cardName  = args['name'] or p.get{cardCode, "name"}
    local indicator = args['indicator'] or "true" -- Иконка-индикатор стоимости
    local entire    = args['entire'] -- Поиск по картам обучения
    local nolink    = args['nolink']
    local nott      = args['nott']   -- Вывод всплывающей подсказки
    local text      = args['text'] or cardName
    local articleLink = args['link'] or (cardName .. " (Legends of Runeterra)")
    local linkText
    if(nolink) then
    	linkText = args['text']
    else
    	linkText = formatnil('[[%s|%s]]', articleLink, text)
    end
    
    local link
    
    if(indicator == "true") then
    	local firstCardCode = string.sub(cardCode, 1, (string.find(cardCode,',',1,true) or 0) - 1)
    	firstCardCode = string.sub(firstCardCode, 1, (string.find(firstCardCode,'-',1,true) or 0) - 1)
    	
    	args['cost'] = p.get{firstCardCode, "cost"}
    	args['supertype'] = p.get{firstCardCode, "supertype"}
    	args['type'] = p.get{firstCardCode, "type"}
    	
    	if(args["type"] == "Ловушка" or 
		args["type"] == "Поощрение" or 
		args["type"] == "Исток" or 
		args["type"] == "Способность") then
    		args["cost"] = ""
    	end
    	
    	local INDICATOR_IMAGE = 'File:LoR Non-Champion Non-Spell Indicator.png'
    	local INDICATOR_WIDTH = 20
    	local INDICATOR_LEFT = 0
    	local INDICATOR_SPACE = '&nbsp;'
    	
    	if args['type'] == "Боец" and args['supertype'] == "Чемпион" then
    		INDICATOR_IMAGE = "File:LoR Champion Indicator.png"
    		INDICATOR_WIDTH = 30
    		INDICATOR_LEFT = 1.5
    		INDICATOR_SPACE = ''
    	elseif args['type'] == "Способность" then
    		INDICATOR_IMAGE = "File:Keyword Skill.png"
    	elseif args['type'] == "Заклинание" then
			local keywords = p.get{firstCardCode, "keywords"}
			local k_found = false

			if keywords then
				for _, keyword in pairs(keywords) do
					if keyword == "Медленное" then
						k_found = true
                        break
                    end
                end
			end

			if k_found then
				INDICATOR_IMAGE = "File:LoR Spell Slow Indicator.png"
				INDICATOR_WIDTH = 24
				INDICATOR_LEFT = 2
			else
				INDICATOR_IMAGE = "File:LoR Spell Non-Slow Indicator.png"
				INDICATOR_WIDTH = 23
				INDICATOR_LEFT = 1
			end
    	end
    	
    	local indicatorLink = formatnil('[[%s|%s|link=%s]]', INDICATOR_IMAGE, INDICATOR_WIDTH .. 'px', articleLink)
    	
    	link = mw.html.create('span')
    		:addClass("lor-card-icon")
    		:css("white-space", "nowrap")
    		:done()
    	if(not(nott)) then
    		link:attr("data-param", cardCode)
    	end
    	
    	local indicatorNode = mw.html.create('span')
    		:cssText("position:relative;text-indent:0;")
    		:wikitext(indicatorLink .. INDICATOR_SPACE)
    		:tag('span') -- Стили для позиционирования цифры в кружке-иконке
    			:css('position', 'absolute')
    			:css('left', INDICATOR_LEFT .. "px")
    			:css('top', '50%')
    			:css('user-select', 'none')
    			:css('line-height', '1.5')
    			:css('color', 'white')
    			:css('font-weight', 'normal')
    			:css('text-align', 'center')
    			:css('width', '20px')
    			:css('height', '20px')
    			:css('font-size', '14px')
    			:css('font-style', 'normal')
    			:css('font-family', 'Rubik')
    			:cssText("-ms-transform:translateY(-50%); transform:translateY(-50%);")
    			:wikitext(args['cost'])
    			:done()
    		:done()
    	link
    		:node(indicatorNode)
    		:wikitext(linkText)
    		:done()
    else
	    link = mw.html.create('span')
	        :addClass('lor-card-icon')
	        :attr('data-param', cardCode)
	        :wikitext(linkText)
	end
    
    return link
end

function p.categorizeCardPage(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	
	local cardCode = args[1] or args["code"]
	local cardData = p.get{cardCode, "cardData"}
	
	local lang = mw.language.new("ru")
	local categoryList = {}
	
	if(cardData == nil) then
		return userError("Код " .. cardCode .. " не распознан в Модуль:LoRData", "LuaError")
	end
	
	-- Набор
	table.insert(categoryList, "LoR Набор " .. tostring(p.getSetFromCardcode{cardCode}))
	
	-- Формат
	if(cardData.formats ~= nil) then
		local formatList = cardData.formats
		for i, v in ipairs(formatList) do
			if(v == "Стандартный") then table.insert(categoryList, "LoR Стандартный формат") end
		end
	end
	
	-- Регион
	if(cardData.regions ~= nil) then
		local regionList = cardData.regions
		for i, v in ipairs(regionList) do
			table.insert(categoryList, "LoR " .. v)
		end
	else
		table.insert(categoryList, "LoR " .. p.getRegionFromCardcode{cardCode})
	end
	
	-- Тип
	if(cardData.supertype == "Чемпион") then
		table.insert(categoryList, "Чемпионы LoR")
	else
		local cardType = cardData.type
		local TYPE_PLURALS = {
			["боец"]        = "Сторонники",
			["сторонник"]   = "Сторонники",
			["заклинание"]  = "Заклинания",
			["место силы"]  = "Места силы",
			["способность"] = "Способности",
			["ловушка"]     = "Ловушки",
			["поощрение"]   = "Поощрения",
			["снаряжение"]  = "Снаряжение",
			["исток"]       = "Истоки",
		}
		table.insert(categoryList, "LoR " .. (TYPE_PLURALS[lang:lc(cardType)]) or cardType)
	end
	-- Группы обрабатываются в organizeSubtypes
	
	-- Редкость
	local cardRarity = cardData.rarity
	local RARITY_PLURALS = {
		["обычная"]   = "Обычные",
		["редкая"]    = "Редкие",
		["эпическая"] = "Эпические",
	}
	if(RARITY_PLURALS[lang:lc(cardRarity)]) then
		table.insert(categoryList, "LoR " .. RARITY_PLURALS[lang:lc(cardRarity)] .. " карты")
	end
	-- Если карта удалена - выйти и всё
	if(cardData.removed == true) then
		table.insert(categoryList, "Удаленные карты LoR")
	end
	
	return lib.tbl_concat{
		["tbl"] = categoryList, 
		["prepend"] = "[[Категория:",
		["sep"] = "false",
		["append"] = "]]"
	}
end

function p.getRandomCard()
	local cardList = _filterCards(p.collectLoRData(), { collectible = "true" })
    local cardCodes = {}
    for cardId in pairs(cardList) do
    	table.insert(cardCodes, cardId)
    end
    
    local randomCardId
    -- Random
    math.randomseed(os.time())
	math.random(); math.random(); math.random()
	for x = 1,10 do
	    randomCardId = cardCodes[math.random(1, #cardCodes)]
	end

    local randomCard = cardList[randomCardId]
    local link = mw.ustring.format("%s (Legends of Runeterra)", randomCard.name)
    local image = mw.ustring.format("[[File:%s.png|250px|link=%s]]", randomCardId, link)
    local caption = mw.ustring.format("[[%s|%s]]", link, randomCard.name)
    
    return mw.getCurrentFrame():preprocess(image .. "<br />" ..caption)
end

-- Создает простую таблицу-список карт в синтаксисе MediaWiki
-- Для построения используется синтаксис MediaWiki, а не HTML, чтобы упростить дальнейшие правки готовой таблицы
-- Используется для Проект Транскрипция (Legends of Runeterra)
function p.createSimpleWikitable(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	local s_set     = tonumber(args["set"]) or nil
    local s_region  = args["region"] or nil
    local s_expansion = args["expansion"] or nil
    local append    = (args["append"] == "true")
    
    
    local wikiTable = ""
    if(not(append)) then
    	wikiTable = [[
{| class="wikitable sortable" width="98%"
|-
! Название !! Статус !! Комментарий
|-
]]
	end
    local tableRows = {}
	
    local lorData   = p.collectLoRData()
    local filteredCards = {}
    for cardcode in pairs(lorData) do
        table.insert(filteredCards, cardcode)
    end
    table.sort(filteredCards)
    
    -- Таблица уникальных имен
    local uniqueSet = {}
    
    for _, cardcode in pairs(filteredCards) do
	    repeat
	        local t        = lorData[cardcode]
	        local region   = t.regions or { p.getRegionFromCardcode{cardcode} }
	        local set      = p.getSetFromCardcode{cardcode}
	        local expansion= t.expansion
	        
	        if(uniqueSet[t.name] == nil) then
	        	uniqueSet[t.name] = true
	        else
	        	break
	        end
	        
	        -- Исключить заклинания, места силы и способности
	        if(t.type ~= "Боец") then break end
	        
	        -- Проверка региона
	        if(s_region ~= nil) then
	        	if(not(lib.has(region, s_region))) then
	            	break
	        	end
	        end
	        
	        -- Проверка набора
	        if(s_set ~= nil and s_set ~= set) then
	            break
	        end
	        
	        if(s_expansion ~= nil and s_expansion ~= expansion) then
	        	break
	        end
	        
	        local cardLink = mw.ustring.format("{{Строка Проекта Транскрипция LoR|[[%s (Legends of Runeterra)|%s]]||}}", t.name, t.name)
	        table.insert(tableRows, tostring(cardLink))
	        break
	    until true
    end
    wikiTable = wikiTable .. table.concat(tableRows, "\r\n") .. "\r\n" .. "|}"
    return frame:preprocess(wikiTable)
end

function p.unitTest()
	local lorData = p.collectLoRData()
	for k, v in pairs(lorData) do
		-- Некоторая функция для теста
		p.formatSet{k}
	end
end

function p.generateRedirectLink(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else args = lib.arguments(frame.args) end
	
	local cardCode = args[1]
	local lorData = p.collectLoRData()
	
	if(not(lorData[cardCode])) then
		return
	end
	
	local link = ""
	if(lorData[cardCode].supertype == "Чемпион") then
		link = lorData[cardCode].name .. "/LoR"
	else
		link = lorData[cardCode].name .. " (Legends of Runeterra)"
	end
	
	return "#REDIRECT [[" .. link .. "]]"
end

-- Сравнивает два JSON на предмет различий между версиями
function p.compareJson(frame) 
	local args; if frame.args == nil then args = lib.arguments(frame) else 
        if frame:getParent().args ~= nil then args = lib.arguments(lib.mergeFrames(frame, frame:getParent())) else args = lib.arguments(frame.args) end
	end
	local json = require("Модуль:JSON")
	
	local function jsonToLua(doc)
		local cardData = {}
		for _, data in pairs(doc) do
			cardData[data.cardCode] = data
		end
    	return cardData
	end
	
    local firstTitle = mw.title.new(args[1])
    local firstJson  = json.decode(firstTitle:getContent())
    local firstLua   = jsonToLua(firstJson)
    
    local secondTitle = mw.title.new(args[2])
    local secondJson  = json.decode(secondTitle:getContent())
    local secondLua   = jsonToLua(secondJson)
    
    local resultTable = {}
    table.insert(resultTable, args[1] .. " → " .. args[2])
    
    for k, v in lib.pairsByAlphabeticalKeys(firstLua) do
    	repeat
	    	-- Наличие самой карты
	    	if(secondLua[k] == nil) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"Карта %s (%s) отсутствует",
	    			v.name,
	    			k
	    			))
	    		break
	    	end
	    	-- Сила атаки
	    	if(secondLua[k].attack ~= v.attack) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Сила атаки — %d → %d",
	    			v.name,
	    			k,
	    			v.attack,
	    			secondLua[k].attack
	    			))
	    	end
	    	-- Здоровье
	    	if(secondLua[k].health ~= v.health) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Здоровье — %d → %d",
	    			v.name,
	    			k,
	    			v.health,
	    			secondLua[k].health
	    			))
	    	end
	    	-- Стоимость
	    	if(secondLua[k].cost ~= v.cost) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Стоимость — %d → %d",
	    			v.name,
	    			k,
	    			v.cost,
	    			secondLua[k].cost
	    			))
	    	end
	    	-- Навыки
	    	if(table.concat(secondLua[k].keywords,",") ~= table.concat(v.keywords,",")) then
	    		local oldStr = table.concat(v.keywords,",&nbsp;")
	    		if(oldStr == "") then oldStr = "(Нет)" end
	    		local newStr = table.concat(secondLua[k].keywords,",&nbsp;")
	    		if(newStr == "") then newStr = "(Нет)" end
	    		
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Навыки — %s → %s",
	    			v.name,
	    			k,
	    			oldStr,
	    			newStr
	    			))
	    	end
	    	-- Членство в группах
	    	if(table.concat(secondLua[k].subtypes,",") ~= table.concat(v.subtypes,",")) then
	    		local oldStr = table.concat(v.subtypes,",&nbsp;")
	    		if(oldStr == "") then oldStr = "(Нет)" end
	    		local newStr = table.concat(secondLua[k].subtypes,",&nbsp;")
	    		if(newStr == "") then newStr = "(Нет)" end
	    		
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Группы — %s → %s",
	    			v.name,
	    			k,
	    			oldStr,
	    			newStr
	    			))
	    	end
	    	-- Описание
	    	if(not(lib.compareStrings(secondLua[k].descriptionRaw, v.descriptionRaw, {["ignoreWhitespaceType"] = true}))) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Описание — \"%s\"\"%s\"",
	    			v.name,
	    			k,
	    			v.descriptionRaw,
	    			secondLua[k].descriptionRaw
	    			))
	    	end
	    	-- Условие повышения уровня
	    	if(not(lib.compareStrings(
	    		secondLua[k].levelupDescriptionRaw, 
	    		v.levelupDescriptionRaw, 
	    		{["ignoreWhitespaceType"] = true}
	    		))) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Условие повышения уровня — \"%s\"\"%s\"",
	    			v.name,
	    			k,
	    			v.levelupDescriptionRaw,
	    			secondLua[k].levelupDescriptionRaw
	    			))
	    	end
	    	-- Ротация
	    	local callStatus, a = pcall(table.concat, secondLua[k].formats,",")
	    	if(not(callStatus)) then
	    		table.insert(resultTable, k .. " - не указаны форматы")
	    		break
	    	end
	    	local b = table.concat(v.formats or {},",")
	    	if(a ~= b) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Форматы — %s → %s",
	    			v.name,
	    			k,
	    			table.concat(v.formats or {},",&nbsp;"),
	    			table.concat(secondLua[k].formats,",&nbsp;")
	    			))
	    	end
	    	-- Подпись
	    	if(not(lib.compareStrings(secondLua[k].flavorText, v.flavorText, {["ignoreWhitespaceType"] = true}))) then
	    		table.insert(resultTable, mw.ustring.format(
	    			"%s (%s) — Подпись — %s → %s",
	    			v.name,
	    			k,
	    			v.flavorText,
	    			secondLua[k].flavorText
	    			))
	    	end
    		break
    	until true
    end
    return lib.tbl_concat{
    	resultTable,
    	["sep"] = "false",
    	["append"] ="<br />"
    }
end

function p.getRegionStatisticsTableCollectibles(frame)
	local args; if frame.args == nil then args = lib.arguments(frame) else 
        if frame:getParent().args ~= nil then args = lib.arguments(lib.mergeFrames(frame, frame:getParent())) else args = lib.arguments(frame.args) end
	end
	
	local s_region = args["region"] or args[1]
	local REGION_LIST = mw.loadData("Модуль:LoRData/regions")
	if(not(REGION_LIST[s_region])) then
		return userError("Регион " .. s_region .. " не найден в Модуль:LoRData/regions", "LuaError")
	end
	
	local regionStatistics = p.getRegionStatistics(s_region)
	local collectiblesTable = mw.html.create("table")
	collectiblesTable
		:addClass("wikitable")
		:addClass("lor-statistics-table")
		:tag("caption")
			:wikitext("Распределение по коллекционируемости")
			:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Коллекционируемые")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.collectibles,
					regionStatistics.collectibles / regionStatistics.total * 100
					))
				:done()
			:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Неколлекционируемые")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.incollectibles,
					regionStatistics.incollectibles / regionStatistics.total * 100
					))
				:done()
			:newline()
		:done()
	return tostring(collectiblesTable)
end

function p.getRegionStatisticsTableTypes(frame)
	local args
	if frame.args == nil then 
		args = lib.arguments(frame) 
	else 
        if frame:getParent().args ~= nil then args = lib.arguments(lib.mergeFrames(frame, frame:getParent())) else args = lib.arguments(frame.args) end
	end
	
	local s_region = args["region"] or args[1]
	local REGION_LIST = mw.loadData("Модуль:LoRData/regions")
	if(not(REGION_LIST[s_region])) then
		return userError("Регион " .. s_region .. " не найден в Модуль:LoRData/regions", "LuaError")
	end
	
	local regionStatistics = p.getRegionStatistics(s_region)
	local collectiblesTable = mw.html.create("table")
	collectiblesTable
		:addClass("wikitable")
		:addClass("lor-statistics-table")
		:tag("caption")
			:wikitext("Распределение по типам (из коллекционируемых)")
			:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Чемпионы")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.champions,
					regionStatistics.champions / (regionStatistics.collectibles) * 100
				))
				:done()
			:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Сторонники")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.followers,
					regionStatistics.followers / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Заклинания")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.spells,
					regionStatistics.spells / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Места силы")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.landmarks,
					regionStatistics.landmarks / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Снаряжение")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.equipments,
					regionStatistics.equipments / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
		:done()
	return tostring(collectiblesTable)
end

function p.getRegionStatisticsTableRarities(frame)
	local args
	if frame.args == nil then 
		args = lib.arguments(frame) 
	else 
        if frame:getParent().args ~= nil then args = lib.arguments(lib.mergeFrames(frame, frame:getParent())) else args = lib.arguments(frame.args) end
	end
	
	local s_region = args["region"] or args[1]
	local REGION_LIST = mw.loadData("Модуль:LoRData/regions")
	if(not(REGION_LIST[s_region])) then
		return userError("Регион " .. s_region .. " не найден в Модуль:LoRData/regions", "LuaError")
	end
	
	local regionStatistics = p.getRegionStatistics(s_region)
	local collectiblesTable = mw.html.create("table")
	collectiblesTable
		:addClass("wikitable")
		:addClass("lor-statistics-table")
		:tag("caption")
			:wikitext("Распределение по редкости")
			:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Обычные")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.commons,
					regionStatistics.commons / (regionStatistics.collectibles) * 100
				))
				:done()
			:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Редкие")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.rares,
					regionStatistics.rares / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Эпические")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.epics,
					regionStatistics.epics / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("Чемпионы")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.1f%%)",
					regionStatistics.champions,
					regionStatistics.champions / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
			:done()
		:done()
	return tostring(collectiblesTable)
end

function p.getRegionStatisticsTableCosts(frame)
	local args
	if frame.args == nil then 
		args = lib.arguments(frame) 
	else 
        if frame:getParent().args ~= nil then args = lib.arguments(lib.mergeFrames(frame, frame:getParent())) else args = lib.arguments(frame.args) end
	end
	
	local s_region = args["region"] or args[1]
	local REGION_LIST = mw.loadData("Модуль:LoRData/regions")
	if(not(REGION_LIST[s_region])) then
		return userError("Регион " .. s_region .. " не найден в Модуль:LoRData/regions", "LuaError")
	end
	
	local regionStatistics = p.getRegionStatistics(s_region)
	local collectiblesTable = mw.html.create("table")
	collectiblesTable
		:addClass("wikitable")
		:addClass("lor-statistics-table")
		:tag("caption")
			:wikitext("Распределение по стоимости (среди коллекционируемых)")
			:done()
			:newline()
		:done()
	
	local costs = regionStatistics.mana
	
	for i, v in ipairs(costs) do
		if(i == 11) then break end
		collectiblesTable
			:tag("tr")
				:tag("th")
					:addClass("lor-statistics-table__left-row")
					:wikitext(i - 1)
					:done()
				:tag("td")
					:addClass("lor-statistics-table__right-row")
					:wikitext(mw.ustring.format(
						"%d (%.2f%%)",
						v,
						v / (regionStatistics.collectibles) * 100
					))
					:done()
				:newline()
	end
	collectiblesTable
		:tag("tr")
			:tag("th")
				:addClass("lor-statistics-table__left-row")
				:wikitext("10+")
				:done()
			:tag("td")
				:addClass("lor-statistics-table__right-row")
				:wikitext(mw.ustring.format(
					"%d (%.2f%%)",
					costs[11],
					costs[11] / (regionStatistics.collectibles) * 100
				))
				:done()
			:newline()
	
	
	return tostring(collectiblesTable)
end

-- Создает таблицу, которая выдает статистику по картам указанного региона
function p.getRegionStatistics(region)
	local regionCards = {}
	local lorData = p.collectLoRData()
	
	for k, v in pairs(lorData) do
		if(p.getRegionFromCardcode{k} == region) then
			table.insert(regionCards, v)
		elseif(v.regions and (v.regions[1] == region or v.regions[2] == region)) then
			table.insert(regionCards, v)
		end
	end
	
	local totalCount = #regionCards
	-- Коллекционируемость
	local collectibleCount = 0
	local incollectibleCount = 0
	
	-- Типы
	local championCount = 0
	local followerCount = 0
	local spellCount = 0
	local skillCount = 0
	local landmarkCount = 0
	local equipmentCount = 0
	-- local originCount = 0
	
	-- Редкости
	local commonCount = 0
	local rareCount = 0
	local epicCount = 0
	
	-- Стоимость (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10+)
	local costCount = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	
	-- Скрытые
	local hiddenCount = 0
	
	-- Подсчет
	for _, v in ipairs(regionCards) do
		if(v.hidden) then
			hiddenCount = hiddenCount + 1
			incollectibleCount = incollectibleCount + 1
		else
			if(v.collectible) then 
				collectibleCount = collectibleCount + 1
				if(v.supertype == "Чемпион") then 
					championCount = championCount + 1
				elseif(v.type == "Боец") then 
					followerCount = followerCount + 1
				end
				if(v.type == "Заклинание") then spellCount = spellCount + 1 end
				if(v.type == "Способность") then skillCount = skillCount + 1 end
				if(v.type == "Место силы") then landmarkCount = landmarkCount + 1 end
				if(v.type == "Снаряжение") then equipmentCount = equipmentCount + 1 end
				
				if(v.rarity == "Обычная") then commonCount = commonCount + 1 end
				if(v.rarity == "Редкая") then rareCount = rareCount + 1 end
				if(v.rarity == "Эпическая") then epicCount = epicCount + 1 end
				
				if(v.cost > 10) then
					costCount[11] = costCount[11] + 1
				else
					costCount[v.cost + 1] = costCount[v.cost + 1] + 1
				end
			else 
				incollectibleCount = incollectibleCount + 1
			end
		end
	end
	
	return {
		["hiddens"] = hiddenCount,
		["collectibles"] = collectibleCount,
		["incollectibles"] = incollectibleCount,
		["champions"] = championCount,
		["followers"] = followerCount,
		["units"] = followerCount + championCount,
		["spells"] = spellCount,
		["skills"] = skillCount,
		["landmarks"] = landmarkCount,
		["equipments"] = equipmentCount,
		["commons"] = commonCount,
		["rares"] = rareCount,
		["epics"] = epicCount,
		["mana"] = costCount,
		["total"] = totalCount
	}
end


function p.collectLoRData()
	local setList = lib.cloneTable(mw.loadData("Модуль:LoRData/sets"))
	local allLoRData = {}
	for k in pairs(setList) do
		-- Проверка на число избавляет от прохода набора 6cde
		if(tonumber(k)) then
			allLoRData = lib.joinTables(allLoRData, mw.loadData("Модуль:LoRData/data/set" .. k))
		end
	end
	return allLoRData
end

function p.toJson(frame)
	local args
	if frame.args == nil then 
		args = lib.arguments(frame) 
	elseif(frame:getParent().args ~= nil) then 
        args = lib.arguments(lib.mergeFrames(frame, frame:getParent())) else args = lib.arguments(frame.args)
	end
	local setNumber = args[1] or args["set"] or "1"
	local json2lua = require("Module:JSON")
	local setData = mw.loadData("Модуль:LoRData/data/set" .. setNumber)
	return json2lua.encode(setData)
end

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