Fire Emblem Heroes Wiki
Advertisement
Template-info Documentation

This module implements Template:Current Events.

  • maps (t0 = nil)
Displays a list of banners for Special Maps active at the given time (defaults to the current time if not given).
  • events (t0 = nil)
Displays a list of banners for events active at the given time (defaults to the current time if not given).
local cargo = mw.ext.cargo
local Datetime = require 'Module:DatetimeUtil'
local List = require 'Module:ListUtil'
local Hash = require 'Module:HashUtil'

-- sort_id1 == 3000
local GHB_REVIVAL = List.to_set {
	'T0003', 'T0001', 'T0004', 'T0007', 'T0002', 'T0005', 'T0006',
	'T0009', 'T0008', 'T0010', 'T0011', 'T0012', 'T0014', 'T0019',
}

local RIVAL_DOMAINS_CYCLE = {'Infantry', 'Armored', 'Cavalry', 'Flying'}

-- reverse appearance order for special maps
-- hero battles -> ghb revivals -> special training -> rival domains -> ghb / bhb / lhb / mhb -> event maps
local MAP_ORDER_FN = {
	function (v) return v.MapGroup == 'Hero Battle' end,
	function (v) return GHB_REVIVAL[v.Map] end,
	function (v) return v.MapGroup == 'Special Training' end,
	function (v) return v.MapGroup == 'Rival Domains' end,
	function (v) return v.MapGroup == 'Grand Hero Battle' or v.MapGroup == 'Bound Hero Battle' or
		v.MapGroup == 'Legendary Hero Battle' or v.MapGroup == 'Mythic Hero Battle' end,
	function (v) return true end,
}

local EVENT_SOURCES = {
	{'VotingGauntlets', "CONCAT('Voting Gauntlet')=kind,   _pageName=page,Name=text,CONCAT('Voting Gauntlet: ',Name)=name"},
	{'TempestTrials',   "CONCAT('Tempest Trials')=kind,    _pageName=page,Name=text,FullName=name"},
	{'TapBattles',      "CONCAT('Tap Battle')=kind,        _pageName=page,Name=text,CONCAT('Illusory Dungeon: ',Name)=name"},
	{'GrandConquests',  "CONCAT('Grand Conquests')=kind,   _pageName=page,          _pageName=name"},
	{'ForgingBonds',    "CONCAT('Forging Bonds')=kind,     _pageName=page,Name=text,CONCAT('Forging Bonds: ',Name)=name"},
	{'RokkrSieges',     "CONCAT('Røkkr Sieges')=kind,      _pageName=page,          _pageName=name"},
	{'LostLore',        "CONCAT('Lost Lore')=kind,         _pageName=page,Name=text,CONCAT('Lost Lore: ',Name)=name,IF(World='World of Heroes',1,0)=isSpoil"},
	{'HallOfForms',     "CONCAT('Hall of Forms')=kind,     _pageName=page,          _pageName=name"},
	{'MjolnirsStrike',  "CONCAT('Mjölnir\\'s Strike')=kind,_pageName=page,          _pageName=name"},
	{'FrontlinePhalanx',"CONCAT('Frontline Phalanx')=kind, _pageName=page,          _pageName=name"},
}



local countdown = function (dt)
	if dt >= 86400 then
		return ('Days left: %d'):format(math.floor(dt / 86400))
	elseif dt >= 3600 then
		return ('Hrs. left: %d'):format(math.floor(dt / 3600))
	else
		return ('Mins. left: %d'):format(math.floor(dt / 60))
	end
end

local eventEntry = function (banner, dt)
	local eventbox = mw.html.create('div'):css('vertical-align', 'middle'):css("margin","5px 35px"):css("display","inline-block")
	eventbox:tag('div'):addClass('img-responsive'):wikitext(banner)
	eventbox:tag('b'):wikitext(countdown(dt))
	return eventbox
end



local maps = function (args, frame)
	local now = args[1] and tonumber(frame:callParserFunction('#time', 'U', args[1])) or os.time()
	local allMaps = cargo.query('Maps,MapDates', 'Map,MapGroup,Maps._pageName=Page,StartTime,EndTime,CycleTime,AvailTime', {
		join = 'Maps._pageName=MapDates._pageName',
		where = ("MapGroup!='Tempest Trials' AND ('%s' BETWEEN StartTime AND EndTime) AND NOT ((CycleTime IS NULL OR AvailTime IS NULL) AND EndTime='%s')"):format(
			Datetime.to_cargo(now), Datetime.MAX_TIME),
		orderBy = 'StartTime,Map',
		limit = 100,
	})
	for _, v in ipairs(allMaps) do
		v.StartTime = Datetime.from_cargo(v.StartTime)
		v.EndTime = Datetime.from_cargo(v.EndTime)
		v.CycleTime = tonumber(v.CycleTime)
		v.AvailTime = tonumber(v.AvailTime)
	end
	List.keep_if(allMaps, function (v)
		if not v.CycleTime or not v.AvailTime then
			return true
		end
		v.StartTime = math.max(v.StartTime, v.StartTime + math.floor((now - v.StartTime) / v.CycleTime) * v.CycleTime)
		v.EndTime = math.min(v.EndTime, v.StartTime + v.AvailTime - 1)
		return now >= v.StartTime and now <= v.EndTime
	end)

	local sortedMaps = {}
	for _, fn in ipairs(MAP_ORDER_FN) do
		List.concat_self(sortedMaps, List.delete_if(allMaps, fn))
	end
	List.reverse_self(sortedMaps)

	-- existing special map banners
	local vbanners = Hash.from_ipairs(cargo.query('_pageData', '_pageName', {
		where = "_pageName RLIKE '^File:Banner V[[:digit:]]+\\.(webp|png)$'",
		limit = 200,
	}), function (v) return mw.ustring.match(v._pageName, 'Banner (V%d+)'), true end)

	return table.concat(List.map(sortedMaps, function (v)
		local bannerName = nil
		if mw.ustring.sub(v.Map, 1, 1) == 'V' then
			bannerName = vbanners[v.Map] and v.Map or 'V0001'
		elseif v.MapGroup == 'Rival Domains' then
			bannerName = 'Rival Domains'
			local num = tonumber(mw.ustring.match(v.Map, '^Q(%d+)$'))
			if num and num >= 9 then
				bannerName = ('Q%04d'):format((num - 1) % 4 + 1)
			end
		elseif v.MapGroup == 'Relay Defense' then
			bannerName = 'R0001'
		end
		local banner = frame:expandTemplate {title = 'Banner HB', args = {v.Page, bannerName = bannerName}}
		return tostring(eventEntry(banner, v.EndTime - now))
	end))
end

local events = function (args, frame)
	local allEvents = {}
	local now = args[1] and tonumber(frame:callParserFunction('#time', 'U', args[1])) or os.time()
	local queryArgs = {where = ("'%s' BETWEEN StartTime AND EndTime"):format(Datetime.to_cargo(now)), groupBy = '_pageName'}
	for _, v in ipairs(EVENT_SOURCES) do
		List.concat_self(allEvents, cargo.query(v[1], v[2] .. ',EndTime', queryArgs))
	end
	for _, v in ipairs(allEvents) do
		v.EndTime = Datetime.from_cargo(v.EndTime)
	end
--	table.sort(allEvents, function (x, y) return x.EndTime < y.EndTime end)

	return table.concat(List.map(allEvents, function (v)
		if v.kind == 'Lost Lore' and v.isSpoil == '1' then v.kind = 'Lost Lore Spoils' end
		local banner = v.kind == 'Tempest Trials' and
			frame:expandTemplate {title = 'Banner TT', args = {bannerName = v.text}} or
			frame:expandTemplate {title = 'Banner Event', args = {bannerType = v.kind, fontSize = 4, text1 = v.text, link = v.page}}
		return tostring(eventEntry(('%s<br>[[%s|%s]]'):format(banner, v.page, v.name), v.EndTime - now))
	end))
end

return require 'Module:MakeMWModule'.makeMWModule {
	maps = maps,
	events = events,
}
Advertisement