Module:Quest

local cargo = mw.ext.cargo local escq = require 'Module:EscQ'.main1 local mf = require 'Module:MF'.main1 local util = require 'Module:Util' local hash = require 'Module:HashUtil' 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 questWikiName = function(sortID, startDate, groupName, questIndex) local y, m, d = mw.ustring.match(tostring(startDate), '^(%d+)%-(%d+)%-(%d+)') if not d then return nil else return mw.ustring.format('%d%d', sortID / 10, sortID % 10) .. y .. m .. d .. ' ' .. mf(groupName) .. ' ' .. mw.ustring.format('%d%d', questIndex / 10, questIndex % 10) end end

local shouldSeparateGroup = function(t1, t2) for _, start1 in ipairs(t1.startTime) do		if list.find(t2.startTime, start1) then return false end if list.find(t2.endTime, Datetime.to_iso8601(Datetime.from_iso8601(start1) - 1)) then return false end end return true end

local displayQuest = function(quests, depth, collapse, frame) local s = "" for _, group in ipairs(quests) do		local tbl = mw.html.create('table'):addClass('wikitable'):addClass('default'):css('text-align', 'center') if collapse then tbl:addClass('mw-collapsed'):addClass('mw-collapsible') end local row = tbl:tag('tr') if #group.rewardHeader > 1 then row:tag('th'):attr('rowspan', 2):wikitext('Availability') row:tag('th'):attr('rowspan', 2):wikitext('Quests') row:tag('th'):attr('colspan', #group.rewardHeader):wikitext('Rewards') row = tbl:tag('tr') for _, rewardHeader in ipairs(group.rewardHeader) do				row:tag('th'):wikitext(rewardHeader) end else row:tag('th'):wikitext('Availability') row:tag('th'):wikitext('Quests') row:tag('th'):wikitext('Rewards') end for iGroup, subGroup in ipairs(group) do

if iGroup ~= 1 then if shouldSeparateGroup(subGroup, group[iGroup-1]) then tbl:tag('tr'):tag('th'):attr('colspan', 2 + #group.rewardHeader):css('height', '.5em'):css('padding', '0') else tbl:tag('tr'):tag('th'):attr('colspan', 2 + #group.rewardHeader):css('height', '.1em'):css('padding', '0') end end for iQuest, quest in ipairs(subGroup.quests) do				row = tbl:tag('tr') if iQuest == 1 then local cell = row:tag('td'):attr('rowspan', #subGroup.quests) for j = 1, #subGroup.startTime do						if j ~= 1 then cell:wikitext(' ') end cell:wikitext(Datetime.ht_range(subGroup.startTime[j], subGroup.endTime[j])) if subGroup.nbRecur[j] > 1 then cell:wikitext(' (' .. subGroup.nbRecur[j] .. ' x ' .. subGroup.timeRecur[j] .. ' days)') end end if subGroup.difficulty and subGroup.difficulty ~= "" then cell:wikitext(' (' .. subGroup.difficulty .. ')')					end end row:tag('td'):css('text-align', 'left'):wikitext(quest.text) for i = 1, #group.rewardHeader do					row:tag('td'):css('text-align', 'right'):wikitext(quest.reward[i]) end end end s = s .. '' .. (group.title or '') .. '' .. tostring(tbl) end

return s end

local groupQuest = function (quests) local i = 1 while i <= #quests do		local index = list.find_if(quests, function(gr, iGr)			return iGr < i and gr.title == quests[i].title		end) if index then table.insert(quests[index], quests[i]) table.remove(quests, i)		else table.insert(quests, i, {				title = quests[i].title,				[1] = quests[i],			}) table.remove(quests, i + 1) i = i + 1 end end

for _, group in ipairs(quests) do		for _, subGroup in ipairs(group) do			subGroup.rewardHeader = { '1' } subGroup.nbRecur = { 1 } subGroup.timeRecur = { ((Datetime.from_iso8601(subGroup.endTime[1] or ) or 0) - 									(Datetime.from_iso8601(subGroup.startTime[1] or ) or 0) + 1) / 86400 } for _, quest in ipairs(subGroup.quests) do				quest.reward = { quest.reward } end end -- Ungroup quests with several time for _, subGroup in ipairs(group) do			while #subGroup.startTime > 1 do				table.insert(group, {					title = group.title,					startTime = { subGroup.startTime[2] },					endTime = { subGroup.endTime[2] },					rewardHeader = { '1' },					nbRecur = { 1 },					timeRecur = { subGroup.timeRecur[1] },					quests = subGroup.quests,				}) table.remove(subGroup.startTime, 2) table.remove(subGroup.endTime, 2) end end table.sort(group, function(t1, t2) return (t1.startTime[1] or ) < (t2.startTime[1] or ) end) -- Group recurring quests i = 2 while i <= #group do			local subGroup = group[i-1] if (Datetime.from_iso8601(group[i].startTime[1] or ) or 0) == ((Datetime.from_iso8601(subGroup.endTime[1] or ) or 0) + 1) then if group[i].timeRecur[1] == subGroup.timeRecur[1] then subGroup.nbRecur[1] = subGroup.nbRecur[1] + 1 end subGroup.endTime[1] = group[i].endTime[1] local increaseRewardHeader = not list.all(group[i].quests, function(qu) 					for _, quest in ipairs(subGroup.quests) do						if qu.text == quest.text and qu.difficulty == quest.difficulty and (not qu.reward[1] or							hash.any(quest.reward, function(reward) return reward == qu.reward[1] end)) then								return true						end					end				end) if increaseRewardHeader then local previousDay = mw.ustring.match(subGroup.rewardHeader[#subGroup.rewardHeader], '-(%d+)') or subGroup.rewardHeader[#subGroup.rewardHeader] table.insert(subGroup.rewardHeader, tostring(tonumber(previousDay) + 1)) for currentQuest, quest in ipairs(group[i].quests) do						local iQu = list.find_if(subGroup.quests, function(qu) 							return qu.text == quest.text and qu.difficulty == quest.difficulty and 								not qu.reward[#subGroup.rewardHeader]						end) if not iQu then local insertIndex = list.find_if(subGroup.quests, function(qu)								if qu.reward[#subGroup.rewardHeader - 1] then									currentQuest = currentQuest - 1								end								return currentQuest == 0							end) table.insert(subGroup.quests, (insertIndex or #subGroup.rewardHeader) + 1, { 								text = quest.text,								difficulty = quest.difficulty,								reward = { [#subGroup.rewardHeader] = quest.reward[1] },							}) elseif not subGroup.quests[iQu].reward[#subGroup.rewardHeader] then table.insert(subGroup.quests[iQu].reward, #subGroup.rewardHeader, quest.reward[1]) end end end table.remove(group, i)			else i = i + 1 end end local gIndex = list.max(group, function(t1, t2) return #t1.rewardHeader < #t2.rewardHeader end) group.rewardHeader = group[gIndex].rewardHeader

-- Group similar quests with different times for i = #group, 1, -1 do			local index = list.find_if(group, function (subGr)				if #subGr.quests ~= #group[i].quests then					return false				end				for j = 1, #subGr.quests do					if subGr.quests[j].text ~= group[i].quests[j].text or						not list.equal(subGr.quests[j].reward, group[i].quests[j].reward) or						subGr.quests[j].difficulty ~= group[i].quests[j].difficulty then							return false					end				end				return true			end, i+1)

if index then list.concat_self(group[index].startTime, group[i].startTime) list.concat_self(group[index].endTime, group[i].endTime) list.concat_self(group[index].nbRecur, group[i].nbRecur) list.concat_self(group[index].timeRecur, group[i].timeRecur) table.remove(group, i)			end end -- Ungroup quests with several difficulties if group[1].quests[1].difficulty then for iSG, subGroup in ipairs(group) do				subGroup.difficulty = subGroup.quests[1].difficulty for iQu = #subGroup.quests, 1, -1 do					if subGroup.quests[iQu].difficulty ~= subGroup.difficulty then local iSubGr = list.find_if(group, function (subGr)							return subGr.difficulty == subGroup.quests[iQu].difficulty and 								list.equal(subGr.startTime, subGroup.startTime) and									list.equal(subGr.endTime, subGroup.endTime)						end) if iSubGr then table.insert(group[iSubGr].quests, 1, subGroup.quests[iQu]) else table.insert(group, iSG + 1, {								startTime = subGroup.startTime,								endTime = subGroup.endTime,								nbRecur = subGroup.nbRecur,								timeRecur = subGroup.timeRecur,								difficulty = subGroup.quests[iQu].difficulty,								quests = { subGroup.quests[iQu] },							}) end table.remove(subGroup.quests, iQu) end end end end

-- Reorder times and groups for _, subGroup in ipairs(group) do			i = 2 while i <= #subGroup.startTime do				if subGroup.startTime[i] < subGroup.startTime[i-1] then subGroup.startTime[i],subGroup.startTime[i-1] = subGroup.startTime[i-1],subGroup.startTime[i] subGroup.endTime[i],subGroup.endTime[i-1] = subGroup.endTime[i-1],subGroup.endTime[i] subGroup.nbRecur[i],subGroup.nbRecur[i-1] = subGroup.nbRecur[i-1],subGroup.nbRecur[i] subGroup.timeRecur[i],subGroup.timeRecur[i-1] = subGroup.timeRecur[i-1],subGroup.timeRecur[i] if i ~= 2 then i = i-1 end else i = i+1 end end end table.sort(group, function(t1, t2) return (t1.startTime[1] or ) < (t2.startTime[1] or ) end) end end

local parseReward = function (quests, iconsize, frame) for _, quest in ipairs(quests) do		quest.StartTime = Datetime.to_iso8601(Datetime.from_cargo(quest.StartTime)) quest.EndTime = Datetime.to_iso8601(Datetime.from_cargo(quest.EndTime)) quest.Rewards = end end

local queryQuest = function (args, frame) local pageName = mw.title.getCurrentTitle.text local listQuests = cargo.query("Quests,QuestRewards,Units,Skills", 		"Quests.GroupName=GroupName,Text,Difficulty," .. 		"Amount,Item,IFNULL(CONCAT(Units.Name,': ',Units.Title),Units.Name)=RewardUnit,Rarity,Skills.Name=SacredSeal,FBRank,Accessory," ..		"Quests.StartTime=StartTime, Quests.EndTime=EndTime", {			join = "Quests.WikiName=QuestRewards.WikiName,QuestRewards.Unit=Units.WikiName,QuestRewards.SacredSeal=Skills.WikiName",			where = args[1] or ("Stage = '%s' OR Quests.Unit = '%s'"):format(escq(pageName), escq(pageName)),			orderBy = "Quests.WikiName",			limit=1000,		}) parseReward(listQuests) local quests = {} for _, quest in ipairs(listQuests) do		quest.Rewards = RewardText({quest.Rewards, size = args.iconsize, iconfirst = '1'}, frame) local _, group = list.find_if(quests, function(gr)			return gr.startTime[1] == quest.StartTime and gr.endTime[1] == quest.EndTime and gr.title == quest.GroupName		end) if group then if list.none(group.quests, function(qu) 				return qu.text == quest.Text and qu.reward == quest.Rewards and qu.difficulty == quest.Difficulty			end) then table.insert(group.quests, { text = quest.Text, reward = quest.Rewards, difficulty = quest.Difficulty }) end else table.insert(quests, {				startTime = { quest.StartTime },				endTime = { quest.EndTime },				title = quest.GroupName,				quests = {					{ text = quest.Text, reward = quest.Rewards, difficulty = quest.Difficulty },				},			}) end end

groupQuest(quests)

return displayQuest(quests, tonumber(args.depth or 3), args.collapse, frame) end

local defineQuest = function (args, frame) local quests = parseArgs(args[1] or "[]") for _, group in ipairs(quests) do		local startTime = {} mw.ustring.gsub(group.startTime or '', "([^,]+)", function(time) table.insert(startTime, time) end) local endTime = {} mw.ustring.gsub(group.endTime or '', "([^,]+)", function(time) table.insert(endTime, time) end) if not group.availTime then group.startTime = startTime group.endTime = endTime else group.startTime = {} group.endTime = {} for iTime = 1, #startTime do				local time = Datetime.from_iso8601(startTime[iTime]) local maxTime = Datetime.from_iso8601(endTime[iTime] or "") or os.time while time < maxTime do					table.insert(group.startTime, Datetime.to_iso8601(time)) table.insert(group.endTime, Datetime.to_iso8601(time + group.availTime - 1)) time = time + (group.cycleTime and group.cycleTime or group.availTime) end end end

local unitTable = {} for iQuest, quest in ipairs(group.quests) do			quest.reward = Reward.normalize(quest.reward) quest.text = ' .. (quest.name or '') ..  ' .. (quest.description or '') .. (quest.times and quest.times ~= 1 and (' (' .. quest.times .. ' times)') or '') if quest.startTime then startTime = {} mw.ustring.gsub(quest.startTime, "([^,]+)", function(time) table.insert(startTime, time) end) quest.startTime = startTime end if quest.endTime then endTime = {} mw.ustring.gsub(quest.endTime, "([^,]+)", function(time) table.insert(endTime, time) end) quest.endtime = endTime end

for iTime = 1, #group.startTime do				if not (args['no cargo'] or mw.title.getCurrentTitle.namespace ~= 0) then local wikiName = questWikiName(group["sort"], group.startTime[iTime], group.wikiName or group.title, iQuest) Reward.define(quest.reward, 'Quests', {						quest = wikiName,						from = quest.startTime and quest.startTime[iTime] or group.startTime[iTime],						to = quest.endTime and quest.endTime[iTime] or group.endTime[iTime]					}, frame) frame:expandTemplate({ title = "QuestDefinition", args = { 						wikiName = wikiName,						group = group.title,						text = quest.text,						stage = quest.stage,						unit = quest.unit,						startTime = group.startTime[iTime],						endTime = group.endTime[iTime] or Datetime.to_iso8601(0x7FFFFFFF),						difficulty = quest.difficulty,					} }) end end

-- Add reward units to unitTable for _, r in ipairs(quest.reward) do				if r.kind == 'Hero' then if unitTable[r.hero] ~= nil then if unitTable[r.hero][r.rarity] ~= nil then unitTable[r.hero][r.rarity] = unitTable[r.hero][r.rarity] + 1 else unitTable[r.hero][r.rarity] = 1 end else unitTable[r.hero] = { [r.rarity] = 1 } end end end quest.reward = RewardText({ quest.reward, size = args.iconsize, iconfirst = '1'}, frame) end

-- Define reward units for unit, unitInfo in pairs(unitTable) do			for rarity, amount in pairs(unitInfo) do				frame:expandTemplate({ title = 'DistributedUnit', args = {					unit = unit,					rarity = rarity,					['type'] = 'Quest',					amount = amount,					source = string.format('%s quest (%s)', group.title, os.date('%B %Y', Datetime.from_iso8601(group.startTime[1]))),					from = group.startTime[1],					to = group.endTime[1] or Datetime.to_iso8601(0x7FFFFFFF)				} }) end end end

groupQuest(quests)

return displayQuest(quests, tonumber(args.depth or 3), args.collapse, frame) end

return require 'Module:MakeMWModule'.makeMWModule { questWikiName = questWikiName, defineQuest = defineQuest, queryQuest = queryQuest, }