Fire Emblem Heroes Wiki
Register
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',
	'T0023', 'T0024', 'T0026', 'T0029', 'T0030', 'T0031', 'T0033',
	'T0036', 'T0038', 'T0039', 'T0042', 'T0044', 'T0047', 'T0050',
}

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 List.find({'Grand Hero Battle', 'Bound Hero Battle', 'Legendary Hero Battle',
		'Mythic Hero Battle', 'Legendary & Mythic Hero Battle'}, v.MapGroup) end,
	function (v) return v.MapGroup == 'Limited 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"},
	{'PawnsOfLoki',        "CONCAT('Pawns of Loki')=kind,        _pageName=page,          _pageName=name"},
	{'HeroesJourney',      "CONCAT('Heroes Journey')=kind,       _pageName=page,          _pageName=name"},
	{'EventsSummonerDuels',"SUBSTRING(_pageName,1,16)=kind,      _pageName=page,          _pageName=name"},
	{'BindingWorlds',      "CONCAT('Binding Worlds')=kind,       _pageName=page,          _pageName=name"},
	{'SeersSnare',         "CONCAT('Seer\\'s Snare')=kind,       _pageName=page,          _pageName=name"},
	{'AffinityAutoBattles',"CONCAT('Affinity Auto-Battles')=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,
	})
	List.concat_self(allMaps, cargo.query('LimitedMaps=LM,LimitedMaps__Entry=LME',
		"Map,CONCAT('Limited Hero Battle')=MapGroup,LM._pageName=Page,StartTime,EndTime,GROUP_CONCAT(LME._value SEPARATOR ',')=Entry", {
			join = 'LM._ID=LME._rowID',
			where = ("'%s' BETWEEN StartTime AND EndTime"):format(Datetime.to_cargo(now)),
			orderBy = 'StartTime,Map',
			groupBy = 'LM._ID',
			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
		local limited = 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'
		elseif v.MapGroup == 'Limited Hero Battle' then
			limited = v.Entry
		end
		local banner = frame:expandTemplate {title = 'Banner HB', args = {v.Page, bannerName = bannerName, limited = limited}}
		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