Mobius Final Fantasy Wiki
Advertisement

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("★",  BLACK)
		:gsub("★", BLACK)
		:gsub("★",  BLACK)
		: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

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
Advertisement