Module:SkillAvailabilityTable

local cargo = mw.ext.cargo local CargoUtil = require 'Module:CargoUtil' local Util = require 'Module:Util' local HeroUtil = require 'Module:HeroUtil' local HashUtil = require 'Module:HashUtil' local ListUtil = require 'Module:ListUtil' local split = mw.text.split local trim = mw.text.trim local findStr = mw.ustring.find local p = {}

--[==[ If you make any changes to this module please bug-check to see that the following requirements are statisfied:

Skills not in the regular summoning pool are counted. E.g. Warding Stance 3, which only Sakura: Gentle Nekomanta has (at the time of this writing) is counted Special case: Seasonal weapons are not included (e.g. Ardent Service)

Only skills that are the max skillPos of any Hero are included on the list. Low rank skills like Quick Riposte 1 are removed, but skills like Death Blow 3 (max for Klein) and Death Blow 4 (max for Celica: Warrior Priestess) are counted Special case: If Heroes have multiple different skill trees in one slot, record the skills with max skillPos of each skill tree. This is so that e.g. Robin: Mystery Tactician will record both Gronnwolf+ as well as Tactical Gale. Tactical gale will be later removed due to being exclusive, however Gronnwolf+ will still be listed even if it is not at maxSkillPos.

Heroes non-max skillPos skills will count towards that skill's count, e.g. Micaiah: Priestess of Dawn will still count towards Ardent Sacrifice even though her skill with max skillPos, Sacrifice, is not counted due to being exclusive. Celica: Warrior Priestess will count towards deathblow 3 as well as death blow 4

]==]

function p.main(frame) local summonableCount = {} --[==[ summonableCount[skill wiki name][#]

[3]: Rarity 3 pool (List) [4]: Rarity 4 pool (List) [5]: Rarity 5 pool (List) [6]: Unsummonable (List of limited Heroes with this skill) [7]: Unobtainable (true, false or nil) [8]: Misc skill info (for sorting): {Scategory, SP} [9]: Other Heroes (special, legendary, mythic, etc) ]==]	local maxSkillPositions = {} local exclusiveKeys = {} --stores keys to exclusive skills

local avail = HeroUtil.getLowestRarities {current = true, mask = {nonfocus = true}}

local mainQuery = CargoUtil.full_query(		"Skills=s,UnitSkills=hp,Units=h",		"s.WikiName=skillwikiname,s.Scategory=scate,CONCAT(hp._pageName)=heropage,hp.skillPos=skillpos,CONCAT(h.Properties__full)=properties,s.Exclusive=exclusive,s.SP=sp,CONCAT(s.Required__full)=required",		{			join = "s.WikiName=hp.skill,hp.WikiName=h.WikiName",			where = s.Scategory != 'sacredseal' AND hp.skill IS NOT NULL AND IFNULL(h.Properties__full,) NOT LIKE '%enemy%' AND			         (s.Scategory!='weapon' OR (IFNULL(h.Properties__full,) NOT LIKE '%special%' AND IFNULL(h.Properties__full,'') NOT LIKE '%specDisplay%')), --Exclude seasonal weapons like Ardent Service+			orderBy = "hp.skillPos ASC",		}	)

for k,v in ipairs(mainQuery) do --First loop to collect skillPos data (low rank skills like Quick Riposte 1 are removed, but skills that are max for a Hero like Death Blow 3 AND 4 are kept) if v["exclusive"] ~= "0" then exclusiveKeys[#exclusiveKeys+1] = k --Record keys for exclusive skills for them to be removed later as to not interfere with skillPos data collection end local isMax = false local currentSkillPos = tonumber(v["skillpos"]) or 0

local skillPosHeroData = maxSkillPositions[v["heropage"]] if not skillPosHeroData then maxSkillPositions[v["heropage"]] = {} isMax = true elseif not skillPosHeroData[v["scate"]] then isMax = true elseif currentSkillPos > (skillPosHeroData[v["scate"]][2] or 99) then -- If current row skillPos is bigger than skillPos for previously recorded skillPos for a Hero and skill category local prevSkillData = mainQuery[skillPosHeroData[v["scate"]][1]] if (not ListUtil.find(split(v["required"],","), prevSkillData["skillwikiname"])) and (currentSkillPos - skillPosHeroData[v["scate"]][2] == 1) then --if the current skill doesn't require the previous skill, preemptively initialize data for previous skill (e.g. If Tactical Gale doesn't require Gronnwolf+). For this trick to work it is essential the cargo query is ordered by skillpos ascending summonableCount[prevSkillData["skillwikiname"]] = {nil,nil,nil,nil,nil,nil,nil,{prevSkillData["scate"],tonumber(prevSkillData["sp"])}} end isMax = true end if isMax then maxSkillPositions[v["heropage"]][v["scate"]] = {k, currentSkillPos} -- store current row key as new highest skillPos end end for _,hero in pairs(maxSkillPositions) do --For every skill that is at max skillpos for a Hero, initialize its data for _,catedata in pairs(hero) do			local skillData = mainQuery[catedata[1]] summonableCount[skillData["skillwikiname"]] = {nil,nil,nil,nil,nil,nil,nil,{skillData["scate"],tonumber(skillData["sp"])}} end end maxSkillPositions = nil

for _,v in ipairs(exclusiveKeys) do --Remove data for exclusive skills summonableCount[mainQuery[v]["skillwikiname"]] = nil end exclusiveKeys = nil for k,v in ipairs(mainQuery) do		local properties = split(v["properties"],",") local skillCount = summonableCount[v["skillwikiname"]] if skillCount then if ListUtil.find(properties, "limited") then --If Hero is limited (e.g. Robin: Mystery Tactician) if not skillCount[6] then skillCount[6] = {v["heropage"]} else table.insert(skillCount[6], v["heropage"]) end elseif not ListUtil.any(properties,					function (property)						return property == "special"							or property == "legendary"							or property == "mythic"					end) then --if hero is in normal summoning pool (not limited, special, legendary, mythic) local availInfo = avail[v["heropage"]] for _,rarity in ipairs(availInfo and availInfo.nonfocus or {}) do					local rarityIndex = tonumber(rarity) if rarityIndex then if not skillCount[rarityIndex] then skillCount[rarityIndex] = {} end table.insert(skillCount[rarityIndex], v["heropage"]) end end else if not skillCount[9] then skillCount[9] = {v["heropage"]} else table.insert(skillCount[9], v["heropage"]) end end end end mainQuery = nil --Special cases summonableCount["Legions Axe Plus"] = nil summonableCount["Clarisses Bow Plus"] = nil summonableCount["Berkuts Lance Plus"] = nil summonableCount["Silver Axe"] = nil --Caused by Linus: Mad Dog summonableCount["Silver Dagger"] = nil --Caused by Flora: Cold as Ice

local summonable3HeroesPercent = 36 / HashUtil.count_if(avail, function (v) return ListUtil.find(v.nonfocus, 3) end) local summonable4HeroesPercent = 58 / HashUtil.count_if(avail, function (v) return ListUtil.find(v.nonfocus, 4) end) local summonable5HeroesPercent = 3 / HashUtil.count_if(avail, function (v) return ListUtil.find(v.nonfocus, 5) end)

local evolvableWeaponsQuery = cargo.query(		"WeaponEvolutions",		"EvolvesInto",		{			where = "CostStones IS NOT NULL",			limit=9000		}	) local unobtainableSkillsQuery = cargo.query( --Query for unobtainable skills		"Skills=s,Skills__Required=r,Skills=after,UnitSkills=hp",		"s.WikiName=skillwikiname,s.Scategory=scate,s.SP=sp",		{			join = "s.WikiName=r._value,r._rowID=after._ID,s.WikiName=hp.skill",			where = "after._pageName IS NULL AND s.Exclusive=0 AND s.Scategory != 'sacredseal' AND hp.skill IS NULL",			orderBy = "s.SP DESC",			limit=9000		}	) --Part 2: HTML Table creation local function makeTable(scategory) local tt		if scategory == "passive" then tt = "SkillText" else tt = "SkillPage" end local tbl = mw.html.create("table") :addClass("wikitable") :addClass("sortable") :css("text-align","center") tbl:tag("th"):wikitext("Skill") tbl:tag("th"):wikitext("3★") tbl:tag("th"):wikitext("4★") tbl:tag("th"):wikitext("5★") tbl:tag("th"):wikitext("%") tbl:tag("th"):wikitext("3★ Heroes") tbl:tag("th"):wikitext("4★ Heroes") tbl:tag("th"):wikitext("5★ Heroes") tbl:tag("th"):wikitext("Limited Heroes") tbl:tag("th"):wikitext("Other Heroes") for _,v in ipairs(unobtainableSkillsQuery) do			if findStr( v["scate"], scategory ) and (scategory ~= "weapon" or (scategory == "weapon" and (not ListUtil.any(evolvableWeaponsQuery, function (row) return row["EvolvesInto"] == v["skillwikiname"] end)))) then -- If the skill cannot be evolved from a weapon local row = tbl:tag("tr") row:tag("td") :css("white-space","nowrap") :wikitext(frame:expandTemplate{ title = tt, args = { v["skillwikiname"] } }) row:tag("td") :attr("colspan",9) :wikitext("Unobtainable") end end for skillwikiname,v in HashUtil.sorted_pairs(summonableCount,			function (v1, v2, k1, k2)				local worth1 = (summonable3HeroesPercent * (#(v1[3] or ""))) + (summonable4HeroesPercent * (#(v1[4] or ""))) + (summonable5HeroesPercent * (#(v1[5] or "")))				local worth2 = (summonable3HeroesPercent * (#(v2[3] or ""))) + (summonable4HeroesPercent * (#(v2[4] or ""))) + (summonable5HeroesPercent * (#(v2[5] or "")))				if worth1 < worth2 then --chance					return true				elseif worth1 > worth2 then 					return false				elseif (#(v1[9] or "")) < (#(v2[9] or "")) then --Amount of other Heroes					return true				elseif (#(v1[9] or "")) > (#(v2[9] or "")) then					return false				elseif (#(v1[6] or "")) < (#(v2[6] or "")) then --Amount of limited Heroes					return true				elseif (#(v1[6] or "")) > (#(v2[6] or "")) then					return false				else					return (v1[8][2] or 0) > (v2[8][2] or 0) --SP				end				return false end) do			if findStr( v[8][1], scategory ) then				local row = tbl:tag("tr")				local count3 = #(v[3] or "")				local count4 = #(v[4] or "")				local count5 = #(v[5] or "")				row:tag("td")					:css("white-space","nowrap")					:wikitext(frame:expandTemplate{ title = tt, args = { skillwikiname } })				row:tag("td"):wikitext(count3)				row:tag("td"):wikitext(count4)				row:tag("td"):wikitext(count5)				row:tag("td"):wikitext(("%.2f%%"):format( (summonable3HeroesPercent * count3) + (summonable4HeroesPercent * count4) + (summonable5HeroesPercent * count5) ) )				for rarity=3,5 do					local list = ""					for _,hero in ipairs(v[rarity] or {}) do						list = list .. " "					end					row:tag("td"):wikitext(trim(list))				end				local listLimited = ""				for _,hero in ipairs(v[6] or {}) do					listLimited = listLimited .. " "				end				row:tag("td"):wikitext(trim(listLimited))				local listOther = "" for _,hero in ipairs(v[9] or {}) do listOther = listOther .. " "				end row:tag("td"):wikitext(trim(listOther)) end end return tbl end return ([[==Weapons== %s

Assists
%s

Specials
%s

Passives
%s]]):format(tostring(makeTable("weapon")), tostring(makeTable("assist")), tostring(makeTable("special")), tostring(makeTable("passive"))) end return p