Module:TapBattle

local util = require 'Module:Util' local list = require 'Module:ListUtil' local datetime = require 'Module:DatetimeUtil' local parseArgs = require 'Module:ObjectArg'.parse local RewardText = require 'Module:RewardText'._main local Reward = require 'Module:Reward' local mf = require 'Module:MF'.main1 local escq = require 'Module:EscQ'.main1 local gsub = mw.ustring.gsub local tobool = require 'Module:Bool'.toboolean

local makeTable = function (frame, stages) local isEncore = list.any(stages, function (s) return s.encore end) local a, difficultyCount = list.max(stages, function (stage1, stage2)		local i = list.max({stage1.scores, stage1.bossScores, stage2.score, stage2.bossScores}, function (l1, l2) if l1 and l2 then return #l1 < #l2 else return l2 end end)		return i > 2	end) difficultyCount = math.max(#difficultyCount.scores, #difficultyCount.bossScores, 1)

local tbl = mw.html.create('table'):addClass('wikitable'):css('text-align', 'center') local row = tbl:tag('tr') if not isEncore and difficultyCount == 1 then --one row of header row:tag('th'):wikitext('Stage') row:tag('th'):wikitext('Rewards') row:tag('th'):wikitext(' Background Music ') row:tag('th'):wikitext('BPM ') row:tag('th'):wikitext(frame:expandTemplate { title = "Hover", args = {'Perfect Run', 'The max combo needed to clear the stage with an S rank.'} }) row:tag('th'):wikitext('Boss') row:tag('th'):wikitext('Boss BGM') row:tag('th'):wikitext('Boss BPM') row:tag('th'):wikitext(frame:expandTemplate { title = "Hover", args = {'HP', 'The amount of taps needed to defeat the boss.'} }) else -- two row of header row:tag('th'):attr('rowspan', 2):wikitext('Stage') row:tag('th'):attr(isEncore and 'colspan'or 'rowspan', 2):wikitext('Rewards') row:tag('th'):attr('rowspan', 2):wikitext(' Background Music ') row:tag('th'):attr('rowspan', 2):wikitext('BPM ') row:tag('th'):attr('colspan', difficultyCount):wikitext(frame:expandTemplate { title = "Hover", args = {'Perfect Run', 'The max combo needed to clear the stage with an S rank.'} }) row:tag('th'):attr('rowspan', 2):wikitext('Boss') row:tag('th'):attr('rowspan', 2):wikitext('Boss BGM') row:tag('th'):attr('rowspan', 2):wikitext('Boss BPM') row:tag('th'):attr('colspan', difficultyCount):wikitext(frame:expandTemplate { title = "Hover", args = {'HP', 'The amount of taps needed to defeat the boss.'} }) row = tbl:tag('tr') if isEncore then row:tag('th'):wikitext('Original') row:tag('th'):wikitext('Encore') end for i = 1, difficultyCount do			row:tag('th'):wikitext(util.getDifficulties[i]) end for i = 1, difficultyCount do			row:tag('th'):wikitext(util.getDifficulties[i]) end end for _, stage in ipairs(stages) do		row = tbl:tag('tr') row:tag('td'):wikitext(stage.name or "") row:tag('td'):wikitext(RewardText({ stage.rewards or {} }, frame)) if isEncore then row:tag('td'):wikitext(RewardText({ stage.encore }, frame)) end if stage.bgmCount then local td = row:tag('td'):attr('rowspan', stage.bgmCount) if stage.bgm and stage.bgm ~= true then td:wikitext(frame:expandTemplate { title = 'BGMPage', args = { stage.bgm } }) td:wikitext(' ') else td:attr('colspan', 2):wikitext("—") stage.bpmCount = nil end end if stage.bpmCount then row:tag('td'):attr('rowspan', stage.bpmCount):wikitext(stage.bpm or "") end if stage.bgm == true and #stage.scores == 0 then row:tag('td'):attr('colspan', difficultyCount):wikitext('—') else for i = 1, difficultyCount do				row:tag('td'):wikitext(#stage.scores >= i and stage.scores[i] or "") end end if stage.noBossCount then row:tag('td'):attr('colspan', 3 + difficultyCount):attr('rowspan', stage.noBossCount):wikitext("—") elseif stage.boss then local bosses = {boss = {}, bgm = {}, bpm = {}} gsub(stage.boss, "([^,]+)", function(b)				bosses.boss[#bosses.boss + 1] = frame:expandTemplate { title = 'HeroIcon', args = {name=b, size='40px'} }			 end) gsub(stage.bossBgm or "", "([^,]+)", function(b)				bosses.bgm[#bosses.bgm + 1] = tostring(frame:expandTemplate { title = 'BGMPage', args = { b } })					.. ' '			end) gsub(stage.bossBpm or "", "(%d+)", function(b) bosses.bpm[#bosses.bpm + 1] = b end) row:tag('td'):wikitext(list.reduce(bosses.boss, function (b1, b2) return b1 .. b2 end) or "") if stage.bossBgm then row:tag('td'):wikitext(list.reduce(bosses.bgm, function (b1, b2) return b1 .. ' ' .. b2 end) or "") row:tag('td'):wikitext(list.reduce(bosses.bpm, function (b1, b2) return b1 .. ', ' .. b2 end) or "") else row:tag('td'):attr('colspan', 2):wikitext("—") end for i = 1, difficultyCount do				bosses = {} gsub(#stage.bossScores >= i and stage.bossScores[i] or "", "([^,]+)", function(b) bosses[#bosses + 1] = b end) row:tag('td'):wikitext(list.reduce(bosses, function (s1, s2) return s1 .. "-" .. s2 end)) end end end return tostring(tbl) end

local defineRewards = function (stages) local wikiname = mw.ext.cargo.query('TapBattles', 'WikiName', {		where=("_pageName='%s'"):format(escq(mw.title.getCurrentTitle.text)),		orderBy = "StartTime ASC"	}) for iStage, stage in ipairs(stages) do		Reward.define(stage.rewards, 'TapBattle', {			wikiname = wikiname[1]["WikiName"],			stage = stage.name,			from = datetime.to_iso8601(stage.start[1])		}) if stage.encore and #stage.start > 1 and #wikiname > 1 then Reward.define(stage.encore, 'TapBattle', {			wikiname = wikiname[2]["WikiName"],			stage = stage.name,			from = datetime.to_iso8601(stage.start[2])		}) end end end

local parseStages = function (datas, start, defaultAvail) local stages = {} for iData, data in ipairs(datas) do		stage = { name = data.name or data.floor or data.floors, bgm = data.bgm or data.BGM or data.backgroundMusic or (#stages ~= 0 and stages[#stages].bgm), bpm = data.bpm or data.BPM, rewards = Reward.normalize(data.rewards or "{}"), scores = data.scores or {}, boss = data.boss or data.bosses, bossBgm = data.bossBgm or data.bossBGM, bossBpm = data.bossBpm or data.bossBPM, bossScores = data.bossScores or data.bossHP or {} }		if stage.bgm == "-" or stage.bgm == "—" then stage.bgm = true end local encore = data.encoreRewards or data.encore if encore then stage.encore = Reward.normalize(encore) end if type(stage.scores) ~= "table" or type(stage.bossScores) ~= "table" then return "Scores must be lists with Normal and Hard's scores" end

if data.start or data.startTime then stage.start = data.start or data.startTime elseif #stages == 0 then stage.start = start else stage.start = list.map(stages[#stages].start, function(s) return s end) end if type(stage.start) == "string" then local tbl = {} gsub(stage.start, "%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%dZ", function (s) tbl[#tbl + 1] = datetime.from_iso8601(s) end) stage.start = tbl elseif defaultAvail then if #datas ~= 20 and iData ~= 1 or iData % 4 == 1 and iData > 8 then list.map_self(stage.start, function (v) return v + 86400 end) end end stages[#stages + 1] = stage end

local iBgm, iBpm, iNoBoss = 1, 1, 1 while iBgm <= #stages do		stages[iBgm].bgmCount = 1 for i = iBgm+1, #stages do			if stages[i].bgm and stages[iBgm].bgm ~= stages[i].bgm then break end stages[iBgm].bgmCount = stages[iBgm].bgmCount + 1 end iBgm = iBgm + stages[iBgm].bgmCount end while iBpm <= #stages do		stages[iBpm].bpmCount = 1 for i = iBpm+1, #stages do			if stages[i].bpm and stages[iBpm].bpm ~= stages[i].bpm then break elseif stages[i].bgm ~= stages[iBpm].bgm then stages[i].bpm = stages[iBpm].bpm break end stages[iBpm].bpmCount = stages[iBpm].bpmCount + 1 end iBpm = iBpm + stages[iBpm].bpmCount end while iNoBoss <= #stages do		if not stages[iNoBoss].boss then stages[iNoBoss].noBossCount = 1 for i = iNoBoss+1, #stages do				if stages[i].boss then break end stages[iNoBoss].noBossCount = stages[iNoBoss].noBossCount + 1 end iNoBoss = iNoBoss + stages[iNoBoss].noBossCount else iNoBoss = iNoBoss + 1 end end return stages end

local stages = function (args, frame) local stages = parseArgs(args[1] or args.stages or "[{}]") local defaultAvail = args.defaultAvail == nil or tobool(args.defaultAvail) local startTime if args.start or args.startTime then startTime = args.start or args.startTime else local delay = {} gsub(args.startDelay or args.delay or "0", "%d+", function (nb) delay[#delay] = tonumber(nb) end) startTime = mw.ext.cargo.query('TapBattles', "StartTime", {			where = ("_pageName='%s'"):format(escq(mw.title.getCurrentTitle.text)),			orderBy = "StartTime ASC"		}) list.map_self(startTime, function (t, i) return datetime.from_cargo(t["StartTime"]) + 86400 * (#delay >= i and delay[i] or delay[#delay]) end) end

if type(stages) ~= "table" or not stages[1] or type(stages[1]) ~= "table" then return require 'Module:Error'.error("A list of hash is required") end

stages = parseStages(stages, startTime, defaultAvail) if type(stages) ~= "table" then return require 'Module:Error'.error(tostring(stages)) end

if not (args['no cargo'] or mw.title.getCurrentTitle.namespace ~= 0) then defineRewards(stages) end return makeTable(frame, stages) end

return require 'Module:MakeMWModule'.makeMWModule { stages = stages }