Mobius Final Fantasy Wiki
m (tostring)
m (Use full page name instead.)
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
  +
local mArguments -- lazy load
local getArgs = require("Module:Arguments").getArgs
 
local yesno = require("Module:Yesno")
+
local cargo = require("Module:Cargo")
  +
local cargo_store = cargo.store
  +
local mm = require("Module:Math")
  +
local yesno = require('Module:Yesno')
   
 
local p = {}
 
local p = {}
  +
local black = '★'
 
local white = '☆'
+
local BLACK = mw.ustring.char(0x2605)
  +
local WHITE = mw.ustring.char(0x2606)
  +
local NAMESPACE = mw.title.getCurrentTitle().namespace
  +
local FULLPAGENAME = mw.title.getCurrentTitle().fullText
  +
 
local multis = {1.0, 1.1, 1.2, 1.3, 1.4, 1.7, 2.0, 2.3, 2.6, 3.0}
 
local multis = {1.0, 1.1, 1.2, 1.3, 1.4, 1.7, 2.0, 2.3, 2.6, 3.0}
  +
local cooldowns = {8, 7, 6, 5, 4, 3, 2, 2, 1, 1}
   
local jobtype_icon_paths = {
+
local i18n = {
  +
categories = {
warrior = "Warrior Icon.png",
 
  +
-- Tracking categories
mage = "Mage Icon.png",
 
  +
non_support_with_no_atk = "Non-support abilities with no attack",
ranger = "Ranger Icon.png",
 
  +
non_support_with_no_brk = "Non-support abilities with no break power",
monk = "Monk Icon.png",
 
  +
non_support_with_fixed_atk = "Non-support abilities with fixed attack",
meia = "Mage Icon.png",
 
  +
non_support_with_fixed_brk = "Non-support abilities with fixed break power",
sarah = "Ranger Icon.png",
 
  +
non_support_with_cooldown = "Non-support abilities with cooldown",
sophie = "Monk Icon.png",
 
  +
support_with_varied_atk = "Support abilities with attack",
  +
support_with_varied_brk = "Support abilities with break power",
  +
support_with_fixed_atk = "Support abilities with fixed attack",
  +
support_with_fixed_brk = "Support abilities with fixed break power",
  +
support_with_no_cooldown = "Support abilities with no cooldown",
  +
},
  +
errors = {
  +
rarity_empty = "No rarity specified.",
  +
rarity_not_a_number = "Rarity is not a number: %q",
  +
rarity_out_of_bounds = "Rarity is outside the range of 1-5: %d",
  +
}
 
}
 
}
   
local function round(n)
+
local function number_to_stars(n)
return math.floor(n + 0.5)
+
local n = tonumber(n) or 0
  +
return string.rep(WHITE, n / 10) .. string.rep(BLACK, n % 10)
 
end
 
end
   
local function count_stars(s)
+
local function parse_number(s)
local total = tonumber(s)
+
return tonumber(s) or 0
local _, b_count, w_coun
 
if s == nil then return 0 end
 
if total == nil then
 
_, b_count = string.gsub(s, black, '')
 
_, w_count = string.gsub(s, white, '')
 
total = b_count + w_count * 10
 
end
 
return total
 
 
end
 
end
   
local function number_to_stars(n)
+
local function parse_stars(s)
local total = tonumber(n)
+
local s = tostring(s)
  +
:gsub("★", BLACK)
local w_count = math.floor(total / 10)
 
  +
:gsub("★", BLACK)
local b_count = total % 10
 
  +
:gsub("★", BLACK)
return string.rep(white, w_count) .. string.rep(black, b_count)
 
  +
:gsub("☆", WHITE)
  +
:gsub("☆", WHITE)
  +
:gsub("☆", WHITE)
  +
local pattern = "^%s*(["..BLACK..WHITE.."]*)%s*x?%s*(%d*)%s*$"
  +
local stars, digit = mw.ustring.match(s, pattern)
  +
if digit == "" and stars == "" then
  +
mw.log(string.format("Count not find stars or numbers: %q", s))
  +
return 0
  +
elseif digit == "" and stars ~= "" then
  +
local _, bcount, wcount
  +
_, bcount = string.gsub(stars, BLACK, "")
  +
_, wcount = string.gsub(stars, WHITE, "")
  +
return bcount + wcount * 10
  +
elseif digit ~= "" and (stars == "" or stars == BLACK) then
  +
return tonumber(digit)
  +
elseif digit ~= "" and stars ~= "" then
  +
mw.log(string.format("Number and multiple stars specified: %q", s))
  +
-- Stars were specified multiple times AND digits also follow.
  +
return 0
  +
end
 
end
 
end
   
local function jobtype_icon(s)
+
function p.get_levels(args)
local s = mw.ustring.lower(s or '')
+
local rarity = tostring(args.rarity or '')
  +
if mw.text.trim(rarity) == '' then
local filepath = jobtype_icon_paths[s]
 
  +
error(i18n.errors.rarity_empty)
if filepath then
 
  +
end
return string.format('[[File:%s|20x20px|link=]]', filepath)
 
  +
local jobtype = tostring(args.jobtype):lower()
end
 
  +
local is_support = jobtype == 'support' or jobtype == 'healer'
end
 
   
  +
local rarities = {}
function p.ultimate(frame)
 
  +
local is_fast
local args = getArgs(frame,{
 
  +
for digit, plus in rarity:gmatch('(%d+)(%+?)') do
wrappers = "Template:Ultimate ability stat"
 
  +
digit = tonumber(digit)
})
 
  +
if digit < 1 or digit > 5 then
return p._ultimate(args)
 
  +
error(i18n.errors.rarity_out_of_bounds:format(digit))
  +
end
  +
rarities[#rarities+1] = digit
  +
is_fast = is_fast or plus == '+'
  +
end
  +
if #rarity == 0 then
  +
error(i18n.errors.rarity_not_a_number:format(rarity))
  +
end
  +
local min_rarity = mm._min(unpack(rarities))
  +
local max_rarity = mm._max(unpack(rarities))
  +
local m, c
  +
if is_support then
  +
m, c = 1, 1
  +
else
  +
m, c = 2, 0
  +
end
  +
local start = 1
  +
local stop = m * max_rarity + c
  +
local step = 1
  +
if is_fast then
  +
start = m * min_rarity + c
  +
step = m
  +
end
  +
local levels = {}
  +
for x=start, stop, step do
  +
levels[#levels+1] = x
  +
end
  +
return levels
 
end
 
end
   
function p._ultimate(args)
+
function p._main(args)
  +
-- Load args into locals
local gauge = {160, 155, 150, 140, 135, 130, 125, 110, 100, 80}
 
local name = args.name
+
local jobtype = tostring(args.jobtype):lower()
local jobtype = args.jobtype or args.type
+
local base_atk = parse_number(args.atk or args.attack)
local info = args.info or args.description
+
local base_brk = parse_number(args.brk or args.break_power or args.breakpower)
local atk = args.atk or args.attack
+
local base_crt = parse_stars(args.crt or args.crit_chance or args.critchance)
atk = tonumber(string.match(atk or '', '(%d+)%%?')) or 0
 
local brk = args.brk or args.breakpower
 
brk = tonumber(string.match(brk or '', '(%d+)%%?')) or 0
 
local const_atk = yesno(args.const_attack, false)
 
local const_brk = yesno(args.const_breakpower, false)
 
local critchance = args.critchance
 
local extra = args.extra
 
 
local total = count_stars(critchance)
 
local critstars = number_to_stars(total)
 
 
local tbl = {
 
{"Lv", "Ultimate Gauge", "Attack", "Break Power", "Crit Chance"}
 
}
 
for lv=1,10 do
 
local m = multis[lv]
 
local atk_n = atk * m
 
local brk_n = brk * m
 
   
  +
local is_fixed_atk = yesno(args.const_atk or args.const_attack)
if const_atk then atk_n = atk end
 
  +
local is_fixed_brk = yesno(args.const_brk or args.const_breakpower)
if const_brk then brk_n = brk end
 
  +
local is_support = jobtype == 'support'
local row = {
 
  +
local has_cooldown = args.has_cooldown
lv,
 
  +
if has_cooldown == nil then -- If there is a support with no-cooldown, then this would be "false"
gauge[lv],
 
  +
has_cooldown = is_support
atk_n .. "%",
 
  +
else
brk_n .. "%",
 
  +
has_cooldown = yesno(has_cooldown)
string.format("%s (%d%%)", critstars, total * 5)
 
  +
end
}
 
tbl[#tbl+1] = row
 
end
 
   
local root = mw.html.create('')
+
local lvls = p.get_levels(args)
  +
local atks = {}
root
 
  +
local brks = {}
:wikitext(jobtype_icon(jobtype))
 
  +
local crts = {}
:tag('b')
 
  +
local cds = {}
:wikitext(name)
 
  +
:done()
 
  +
-- Handle fixed and varying stats.
:newline()
 
  +
for i=1,10 do
:tag("div")
 
  +
local atk
:cssText("font-style:italic")
 
  +
if is_fixed_atk then
:wikitext('"' .. (info or '???') .. '"')
 
  +
atk = base_atk
:done()
 
  +
else
:newline()
 
  +
local atk_i = args['atk'..i] or args['attack'..i]
local wikitable = root:tag('table')
 
  +
local atk_m = base_atk * multis[i]
:addClass('wikitable')
 
  +
atk = atk_i or atk_m
for i,row in ipairs(tbl) do
 
  +
end
local tr = wikitable:tag('tr')
 
  +
atks[i] = mm._round(atk)
local tagname = (i == 1) and 'th' or 'td'
 
  +
local brk
for j,col in ipairs(row) do
 
  +
if is_fixed_brk then
tr
 
  +
brk = base_brk
:tag(tagname)
 
  +
else
:wikitext(col)
 
  +
local brk_i = args['brk'..i] or args['breakpower'..i] or args['break_power'..i]
:done()
 
  +
local brk_m = base_brk * multis[i]
end
 
  +
brk = brk_i or brk_m
tr:done():newline()
 
end
+
end
  +
brks[i] = mm._round(brk)
wikitable:done():newline()
 
  +
crts[i] = base_crt
if extra then
 
  +
if has_cooldown then
root:tag('b')
 
  +
cds[i] = args['cd'..i] or args['cooldown'..i] or cooldowns[i]
:wikitext('Added Effects: ')
 
  +
end
root:wikitext(extra)
 
end
+
end
return tostring(root)
 
end
 
   
  +
-- If the stat set contains only one value, then it is a fixed stat.
function p._main(args)
 
local args = args
+
local atk_set = {}
local rarity = args.rarity
+
local atk_len = 0
local atk = args.atk or args.attack
+
local brk_set = {}
  +
local brk_len = 0
local brk = args.brk or args.breakpower
 
  +
-- We're only checking the values at each level.
local crit = args.crit or args.critchance
 
  +
for i,lvl in ipairs(lvls) do
local const_atk = yesno(args.const_attack, false)
 
  +
-- Before insert, check if it's already defined.
local const_brk = yesno(args.const_breakpower, false)
 
  +
local k = atks[lvl]
local has_cooldown = yesno(args.has_cooldown, false)
 
if rarity == nil then
+
if not atk_set[k] then
  +
atk_len = atk_len + 1
mw.log('ability stat: "rarity" argument is null or empty')
 
  +
atk_set[k] = true
return ''
 
end
+
end
  +
k = brks[lvl]
local cooldowns = {8, 7, 6, 5, 4, 3, 2, 2, 1, 1}
 
  +
if not brk_set[k] then
 
  +
brk_len = brk_len + 1
local fast = false
 
  +
brk_set[k] = true
local rarities = {}
 
  +
end
for a,b in string.gmatch(rarity, '(-?%d+)(%+?)') do
 
  +
end
a = tonumber(a)
 
  +
-- no value, or the only value is "0".
if a < 1 or 5 < a then
 
  +
local has_no_atk = atk_len == 0 or (atk_len == 1 and atk_set[0])
error("Rarity outside the range of 1~5: "..a..b)
 
  +
local has_no_brk = atk_len == 0 or (atk_len == 1 and atk_set[0])
elseif b == '+' then
 
  +
-- If only one value, and is not "0".
fast = true
 
  +
local has_fixed_atk = atk_len == 1 and not atk_set[0]
end
 
  +
local has_fixed_brk = brk_len == 1 and not brk_set[0]
rarities[a] = true
 
  +
-- If there are 2 or more different values.
end
 
  +
-- Is "0" a valid value?
atk = tonumber(atk) or 0
 
  +
local has_varied_atk = atk_len > 1 and not atk_set[0]
brk = tonumber(brk) or 0
 
  +
local has_varied_brk = brk_len > 1 and not brk_set[0]
local total = count_stars(crit)
 
  +
-- Create data rows for storage and display.
local critstars = number_to_stars(total)
 
  +
local data_rows = {}
local crit_n = string.format("%s (%d%%)", critstars, total*5)
 
  +
for i,lvl in ipairs(lvls) do
-- If attack is 0, then it's a support ability.
 
  +
local data_row = {
-- If break is 0, then it's a support ability.
 
  +
level = lvl,
local support = atk == 0 or brk == 0
 
  +
attack = atks[lvl],
local show_atk = atk > 0
 
  +
break_power = brks[lvl],
local show_brk = brk > 0
 
  +
crit_chance = crts[lvl],
local show_cooldown = support or (fast and not crit) or has_cooldown
 
  +
cooldown = cds[lvl],
local show_crit = crit ~= nil and total >= 0
 
  +
}
 
  +
data_rows[#data_rows+1] = data_row
local columns = {}
 
  +
end
local rows = {}
 
  +
local level
 
  +
-- Storage
local m, c -- y = mx + c
 
  +
if NAMESPACE == 0 or args.demospace == "main" then
if support then
 
  +
local abilities = mw.ext.cargo.query("abilities", "_pageName", {
m = 1
 
  +
where = string.format("_pageName = %q", FULLPAGENAME),
c = 1
 
  +
})
else
 
  +
if #abilities > 0 then
m = 2
 
  +
for i,data_row in ipairs(data_rows) do
c = 0
 
  +
cargo.store("ability_stats", data_row)
end
 
  +
end
local levels = {}
 
  +
end
if fast then
 
  +
end
for rarity=1,5 do
 
  +
local has_rarity = rarities[rarity]
 
  +
-- Display
if has_rarity then
 
  +
local root = mw.html.create("table")
levels[#levels+1] = rarity * m + c
 
  +
root:addClass("wikitable")
end
 
  +
local header = root:tag("tr")
end
 
  +
header:tag("th"):wikitext("ALv."):done()
else
 
  +
header:tag("th"):wikitext("Attack"):done()
local max_rarity = 0 -- largest rarity
 
  +
header:tag("th"):wikitext("Break Power"):done()
for i=1,5 do
 
  +
header:tag("th"):wikitext("Crit Chance"):done()
if rarities[i] and max_rarity < i then
 
  +
header:tag("th"):wikitext("Cooldown"):done()
max_rarity = i
 
  +
for i,data_row in ipairs(data_rows) do
end
 
  +
local lvl = data_row.level
end
 
  +
local atk = data_row.attack or 0
for level=1, max_rarity * m + c do
 
  +
local brk = data_row.break_power or 0
levels[#levels+1] = level
 
  +
local crt = data_row.crit_chance
end
 
  +
local stars = number_to_stars(crt)
end
 
  +
crt = string.format("%s (%d%%)", stars, crt * 5)
table.insert(columns, 'ALv.')
 
  +
local cd = data_row.cooldown or 0
if show_atk then table.insert(columns, 'Attack') end
 
if show_brk then table.insert(columns, 'Break Power') end
 
if show_crit then table.insert(columns, 'Crit Chance') end
 
if show_cooldown then table.insert(columns, 'Cooldown') end
 
for i,level in ipairs(levels) do
 
local row = {}
 
local m = multis[level]
 
row[#row+1] = level
 
if show_atk then
 
local atk_n = atk
 
if not const_atk then atk_n = round(atk * m) end
 
row[#row+1] = atk_n
 
end
 
if show_brk then
 
local brk_n = brk
 
if not const_brk then brk_n = round(brk * m) end
 
row[#row+1] = brk_n
 
end
 
if show_crit then
 
row[#row+1] = crit_n
 
end
 
if show_cooldown then
 
local cooldown = cooldowns[level]
 
row[#row+1] = cooldown
 
end
 
rows[#rows+1] = row
 
end
 
   
  +
local tbl_row = root:tag('tr')
local wikitable = mw.html.create('table'):addClass('wikitable'):newline()
 
  +
tbl_row:tag("td"):wikitext(lvl):done()
local tr = mw.html.create('tr')
 
  +
tbl_row:tag("td"):wikitext(atk):done()
for i,th in ipairs(columns) do
 
tr:tag('th'):wikitext(th)
+
tbl_row:tag("td"):wikitext(brk):done()
  +
tbl_row:tag("td"):wikitext(crt):done()
end
 
  +
tbl_row:tag("td"):wikitext(cd):done()
wikitable:node(tr):newline()
 
  +
end
for i,row in ipairs(rows) do
 
  +
tr = mw.html.create('tr')
 
  +
-- Categories
for j,td in ipairs(row) do
 
  +
local cats = {}
tr:tag('td'):wikitext(td)
 
  +
if NAMESPACE == 0 or args.demospace == "main" then
end
 
  +
if is_support then
wikitable:node(tr):newline()
 
  +
if has_fixed_atk then
end
 
  +
cats[#cats+1] = i18n.categories.support_with_fixed_atk
 
  +
elseif has_varied_atk then
local cats = {}
 
  +
cats[#cats+1] = i18n.categories.support_with_varied_atk
if const_atk and const_brk then
 
  +
end
cats[#cats+1] = "Abilities with fixed attack and break power"
 
elseif const_atk then
+
if has_fixed_brk then
cats[#cats+1] = "Abilities with fixed attack"
+
cats[#cats+1] = i18n.categories.support_with_fixed_brk
elseif const_brk then
+
elseif has_varied_brk then
cats[#cats+1] = "Abilities with fixed break power"
+
cats[#cats+1] = i18n.categories.support_with_varied_brk
end
+
end
if has_cooldown then
+
if not has_cooldown then
cats[#cats+1] = "Abilities with cooldown"
+
cats[#cats+1] = i18n.categories.support_with_no_cooldown
end
+
end
  +
else
for i,cat in ipairs(cats) do
 
  +
if has_no_atk then
cats[i] = string.format('[[Category:%s]]', cat)
 
  +
cats[#cats+1] = i18n.categories.non_support_with_no_atk
end
 
  +
elseif has_fixed_atk then
wikitable = tostring(wikitable) .. table.concat(cats)
 
  +
cats[#cats+1] = i18n.categories.non_support_with_fixed_atk
return tostring(wikitable)
 
  +
end
  +
if has_no_brk then
  +
cats[#cats+1] = i18n.categories.non_support_with_no_brk
  +
elseif has_fixed_brk then
  +
cats[#cats+1] = i18n.categories.non_support_with_fixed_brk
  +
end
  +
if has_cooldown then
  +
cats[#cats+1] = i18n.categories.non_support_with_cooldown
  +
end
  +
end
  +
end
  +
for i,cat in ipairs(cats) do
  +
cats[i] = string.format('[[Category:%s]]', cat)
  +
end
  +
  +
-- Output
  +
return tostring(root) .. table.concat(cats)
 
end
 
end
   
 
function p.main(frame)
 
function p.main(frame)
  +
mArguments = require("Module:Arguments")
local args = getArgs(frame,{
 
  +
local args = mArguments.getArgs(frame)
wrappers = "Template:Ability stat"
 
  +
return p._main(args)
})
 
return p._main(args)
 
end
 
 
function p._test()
 
local args = {
 
{rarity="4+",attack="450",breakpower="1",critchance="0",has_cooldown="n"},
 
}
 
for i,v in ipairs(args) do
 
mw.log(i, p._main(v))
 
end
 
 
end
 
end
   

Latest revision as of 11:21, 5 February 2019

Tests

YesY All tests passed.

Name Expected Actual
YesY testFastSupportLevels
YesY testFastWarriorLevels
YesY testNormalSupportLevels
YesY testNormalWarriorLevels

local mArguments -- lazy load
local cargo = require("Module:Cargo")
local cargo_store = cargo.store
local mm = require("Module:Math")
local yesno = require('Module:Yesno')

local p = {}

local BLACK = mw.ustring.char(0x2605)
local WHITE = mw.ustring.char(0x2606)
local NAMESPACE = mw.title.getCurrentTitle().namespace
local FULLPAGENAME = mw.title.getCurrentTitle().fullText

local multis = {1.0, 1.1, 1.2, 1.3, 1.4, 1.7, 2.0, 2.3, 2.6, 3.0}
local cooldowns = {8, 7, 6, 5, 4, 3, 2, 2, 1, 1}

local i18n = {
	categories = {
		-- Tracking categories
		non_support_with_no_atk = "Non-support abilities with no attack",
		non_support_with_no_brk = "Non-support abilities with no break power",
		non_support_with_fixed_atk = "Non-support abilities with fixed attack",
		non_support_with_fixed_brk = "Non-support abilities with fixed break power",
		non_support_with_cooldown = "Non-support abilities with cooldown",
		support_with_varied_atk = "Support abilities with attack",
		support_with_varied_brk = "Support abilities with break power",
		support_with_fixed_atk = "Support abilities with fixed attack",
		support_with_fixed_brk = "Support abilities with fixed break power",
		support_with_no_cooldown = "Support abilities with no cooldown",
	},
	errors = {
		rarity_empty = "No rarity specified.",
		rarity_not_a_number = "Rarity is not a number: %q",
		rarity_out_of_bounds = "Rarity is outside the range of 1-5: %d",
	}
}

local function number_to_stars(n)
	local n = tonumber(n) or 0
	return string.rep(WHITE, n / 10) .. string.rep(BLACK, n % 10)
end

local function parse_number(s)
	return tonumber(s) or 0
end

local function parse_stars(s)
	local s = tostring(s)
		:gsub("&#9733;",  BLACK)
		:gsub("&#x2605;", BLACK)
		:gsub("&starf;",  BLACK)
		:gsub("&#9734;",  WHITE)
		:gsub("&#x2606;", WHITE)
		:gsub("&star;",   WHITE)
	local pattern = "^%s*(["..BLACK..WHITE.."]*)%s*x?%s*(%d*)%s*$"
	local stars, digit = mw.ustring.match(s, pattern)
	if digit == "" and stars == "" then
		mw.log(string.format("Count not find stars or numbers: %q", s))
		return 0
	elseif digit == "" and stars ~= "" then
		local _, bcount, wcount
		_, bcount = string.gsub(stars, BLACK, "")
		_, wcount = string.gsub(stars, WHITE, "")
		return bcount + wcount * 10
	elseif digit ~= "" and (stars == "" or stars == BLACK) then
		return tonumber(digit)
	elseif digit ~= "" and stars ~= "" then
		mw.log(string.format("Number and multiple stars specified: %q", s))
		-- Stars were specified multiple times AND digits also follow.
		return 0
	end
end

function p.get_levels(args)
	local rarity = tostring(args.rarity or '')
	if mw.text.trim(rarity) == '' then
		error(i18n.errors.rarity_empty)
	end
	local jobtype = tostring(args.jobtype):lower()
	local is_support = jobtype == 'support' or jobtype == 'healer'

	local rarities = {}
	local is_fast
	for digit, plus in rarity:gmatch('(%d+)(%+?)') do
		digit = tonumber(digit)
		if digit < 1 or digit > 5 then
			error(i18n.errors.rarity_out_of_bounds:format(digit))
		end
		rarities[#rarities+1] = digit
		is_fast = is_fast or plus == '+'
	end
	if #rarity == 0 then
		error(i18n.errors.rarity_not_a_number:format(rarity))
	end
	local min_rarity = mm._min(unpack(rarities))
	local max_rarity = mm._max(unpack(rarities))
	local m, c
	if is_support then
		m, c = 1, 1
	else
		m, c = 2, 0
	end
	local start = 1
	local stop = m * max_rarity + c
	local step = 1
	if is_fast then
		start = m * min_rarity + c
		step = m
	end
	local levels = {}
	for x=start, stop, step do
		levels[#levels+1] = x
	end
	return levels
end

function p._main(args)
	-- Load args into locals
	local jobtype = tostring(args.jobtype):lower()
	local base_atk = parse_number(args.atk or args.attack)
	local base_brk = parse_number(args.brk or args.break_power or args.breakpower)
	local base_crt = parse_stars(args.crt or args.crit_chance or args.critchance)

	local is_fixed_atk = yesno(args.const_atk or args.const_attack)
	local is_fixed_brk = yesno(args.const_brk or args.const_breakpower)
	local is_support = jobtype == 'support'
	local has_cooldown = args.has_cooldown
	if has_cooldown == nil then -- If there is a support with no-cooldown, then this would be "false"
		has_cooldown = is_support
	else
		has_cooldown = yesno(has_cooldown)
	end

	local lvls = p.get_levels(args)
	local atks = {}
	local brks = {}
	local crts = {}
	local cds = {}
	
	-- Handle fixed and varying stats.
	for i=1,10 do
		local atk
		if is_fixed_atk then
			atk = base_atk
		else
			local atk_i = args['atk'..i] or args['attack'..i]
			local atk_m = base_atk * multis[i]
			atk = atk_i or atk_m
		end
		atks[i] = mm._round(atk)
		local brk
		if is_fixed_brk then
			brk = base_brk
		else
			local brk_i = args['brk'..i] or args['breakpower'..i] or args['break_power'..i]
			local brk_m = base_brk * multis[i]
			brk = brk_i or brk_m
		end
		brks[i] = mm._round(brk)
		crts[i] = base_crt
		if has_cooldown then
			cds[i] = args['cd'..i] or args['cooldown'..i] or cooldowns[i]
		end
	end

	-- If the stat set contains only one value, then it is a fixed stat.
	local atk_set = {}
	local atk_len = 0
	local brk_set = {}
	local brk_len = 0
	-- We're only checking the values at each level.
	for i,lvl in ipairs(lvls) do
		-- Before insert, check if it's already defined.
		local k = atks[lvl]
		if not atk_set[k] then
			atk_len = atk_len + 1
			atk_set[k] = true
		end
		k = brks[lvl]
		if not brk_set[k] then
			brk_len = brk_len + 1
			brk_set[k] = true
		end
	end
	-- no value, or the only value is "0".
	local has_no_atk = atk_len == 0 or (atk_len == 1 and atk_set[0])
	local has_no_brk = atk_len == 0 or (atk_len == 1 and atk_set[0])
	-- If only one value, and is not "0".
	local has_fixed_atk = atk_len == 1 and not atk_set[0]
	local has_fixed_brk = brk_len == 1 and not brk_set[0]
	-- If there are 2 or more different values.
	-- Is "0" a valid value?
	local has_varied_atk = atk_len > 1 and not atk_set[0]
	local has_varied_brk = brk_len > 1 and not brk_set[0]
	-- Create data rows for storage and display.
	local data_rows = {}
	for i,lvl in ipairs(lvls) do
		local data_row = {
			level = lvl,
			attack = atks[lvl],
			break_power = brks[lvl],
			crit_chance = crts[lvl],
			cooldown = cds[lvl],
		}
		data_rows[#data_rows+1] = data_row
	end
	
	-- Storage
	if NAMESPACE == 0 or args.demospace == "main" then
		local abilities = mw.ext.cargo.query("abilities", "_pageName", {
			where = string.format("_pageName = %q", FULLPAGENAME),
		})
		if #abilities > 0 then
			for i,data_row in ipairs(data_rows) do
				cargo.store("ability_stats", data_row)
			end
		end
	end
	
	-- Display
	local root = mw.html.create("table")
	root:addClass("wikitable")
	local header = root:tag("tr")
	header:tag("th"):wikitext("ALv."):done()
	header:tag("th"):wikitext("Attack"):done()
	header:tag("th"):wikitext("Break Power"):done()
	header:tag("th"):wikitext("Crit Chance"):done()
	header:tag("th"):wikitext("Cooldown"):done()
	for i,data_row in ipairs(data_rows) do
		local lvl = data_row.level
		local atk = data_row.attack or 0
		local brk = data_row.break_power or 0
		local crt = data_row.crit_chance
		local stars = number_to_stars(crt)
		crt = string.format("%s (%d%%)", stars, crt * 5)
		local cd = data_row.cooldown or 0

		local tbl_row = root:tag('tr')
		tbl_row:tag("td"):wikitext(lvl):done()
		tbl_row:tag("td"):wikitext(atk):done()
		tbl_row:tag("td"):wikitext(brk):done()
		tbl_row:tag("td"):wikitext(crt):done()
		tbl_row:tag("td"):wikitext(cd):done()
	end
	
	-- Categories
	local cats = {}
	if NAMESPACE == 0 or args.demospace == "main" then
		if is_support then
			if has_fixed_atk then
				cats[#cats+1] = i18n.categories.support_with_fixed_atk
			elseif has_varied_atk then
				cats[#cats+1] = i18n.categories.support_with_varied_atk
			end
			if has_fixed_brk then
				cats[#cats+1] = i18n.categories.support_with_fixed_brk
			elseif has_varied_brk then
				cats[#cats+1] = i18n.categories.support_with_varied_brk
			end
			if not has_cooldown then
				cats[#cats+1] = i18n.categories.support_with_no_cooldown
			end
		else
			if has_no_atk then
				cats[#cats+1] = i18n.categories.non_support_with_no_atk
			elseif has_fixed_atk then
				cats[#cats+1] = i18n.categories.non_support_with_fixed_atk
			end
			if has_no_brk then
				cats[#cats+1] = i18n.categories.non_support_with_no_brk
			elseif has_fixed_brk then
				cats[#cats+1] = i18n.categories.non_support_with_fixed_brk
			end
			if has_cooldown then
				cats[#cats+1] = i18n.categories.non_support_with_cooldown
			end
		end
	end
	for i,cat in ipairs(cats) do
		cats[i] = string.format('[[Category:%s]]', cat)
	end
	
	-- Output
	return tostring(root) .. table.concat(cats)
end

function p.main(frame)
	mArguments = require("Module:Arguments")
	local args = mArguments.getArgs(frame)
	return p._main(args)
end

return p