Module:Unit Infobox

local getArgs = require("Module:Arguments").getArgs local Util = require("Module:Util") local cargo = mw.ext.cargo local ListUtil = require("Module:ListUtil") local HashUtil = require "Module:HashUtil" local Tab = require 'Module:Tab' local lang = mw.getContentLanguage local mf = (require 'Module:MF').main1 local escq = (require 'Module:EscQ').main1 local HeroUtil = require 'Module:HeroUtil' local CargoUtil = require "Module:CargoUtil" local p = {}

local games = {} for _,v in ipairs(cargo.query("Games", "_pageName,value1", {groupBy="_pageName",limit=5000})) do	local name = v._pageName -- HACK: will break if page name is ever not the same as the game name games[v._pageName] = { name = name, --entryName = v.EntryName, shortName = (mw.ustring.gsub(name, "Fire Emblem:?%s*", "")), value1 = v.value1 } end

local ENEMY_ALLOWED_PROPERTIES = ListUtil.to_set({"generic","hair","hat","mask","tiara"})

local INTERNAL_ART_NAMES_TO_NAMES = { -- These names come from Fire Emblem Heroes: Summoner's Guide, see https://fire-emblem-heroes.com/ja/img/topics/detail/img_20170531_02.jpg ["Face"] = "Portrait", ["BtlFace"] = "Attack", ["BtlFace_C"] = "Special", ["BtlFace_D"] = "Damage", }

local ART_IMAGE_SIZE = "340px"

local COMMON_ART = {"Face","BtlFace","BtlFace_C","BtlFace_D"} -- Art files all Heroes have

local STAT_TYPES = {"HP","Atk","Spd","Def","Res"}

p.main = function(frame) local args = getArgs(frame, {		removeBlanks = true,		trim = true,		readOnly = true,		wrappers = {			"Template:Hero Infobox",			"Template:Enemy Infobox",			"Template:Hero Infobox/test",			"Template:Enemy Infobox/test",		}	})

local properties = mw.text.split(args.Properties or "", ",") local hasProperty = ListUtil.to_set(properties) local PAGENAME = mw.title.getCurrentTitle.text local mf_PAGENAME = mf(PAGENAME) -- The unit has an enemy variant local isEnemy = args.enemyTagID or args.EnemyInternalID -- The unit has an enemy variant, and NO playable variant local isOnlyEnemy = isEnemy and (not args.ReleaseDate) and (not args.Entries) and (not args.InternalID) and (not args.randomStartTime) local allyPID = args.allyTagID local enemyEID = args.enemyTagID if (not allyPID) and args.TagID and (not isOnlyEnemy) then allyPID = "PID_"..args.TagID if isEnemy then allyPID = allyPID.."味方" end end if (not enemyEID) and args.TagID and isEnemy then enemyEID = "EID_"..args.TagID end local actors = { EN = {}, JP = {} }	local i = nil repeat local allMissing = true for game_lang,t in pairs(actors) do			local actor = args["actor"..(i or "")..game_lang] if actor then t[#t+1] = actor allMissing = false end end if allMissing then break end i = (i or 1)+1 until false local appearances --TODO: Move description parser for appearances into another module that is also callable through wikitext if Util.isNilOrEmpty(args.Appearances) then appearances = {} for pageName,game in pairs(games) do			local index = mw.ustring.find( args.description or "", game.shortName, nil, true ) or mw.ustring.find( args.description or "", (mw.ustring.gsub(game.shortName, "and", "&")), nil, true ) if index then appearances[#appearances+1] = {pageName,index} end end if #appearances == 0 then appearances[1] = {"Fire Emblem Heroes"} end appearances = ListUtil.map_self(ListUtil.sort_by(appearances, function(e) return e[2] end), function(e) return e[1] end) else appearances = mw.text.split(args.Appearances,",") end local entries if Util.isNilOrEmpty(args.Entries) then entries = appearances else entries = mw.text.split(args.Entries,",") end local s = mw.html.create if not isOnlyEnemy then s:wikitext(frame:expandTemplate{ title = "UnitDefinition", args = {			Name=args.Name,			Title=args.Title,			WikiName=frame:expandTemplate{ title = "UnitWikiName", args = { PAGENAME } },			Person=args.Person,			Origin=args.Origin,			ExtraOrigins=args.ExtraOrigins,			GameSort=args.cv1,			CharSort=args.cv2,			TagID=allyPID,			IntID=args.InternalID,			Gender=args.Gender,			WeaponType=args.WeaponType,			MoveType=args.MoveType,			GrowthMod=args.GrowthModifier,			Artist=args.artist,			ActorEN=table.concat(actors.EN,","),			ActorJP=table.concat(actors.JP,","),			AdditionDate=args.AdditionDate or args.ReleaseDate,			ReleaseDate=args.ReleaseDate,			Properties=args.Properties,			Description=args.description,			Appearance=table.concat(appearances,","),			Entries=table.concat(entries,","),		} }) end if isEnemy then local enemyProps = ListUtil.select(properties, function(e) return ENEMY_ALLOWED_PROPERTIES[e] end) enemyProps[#enemyProps+1] = "enemy" s:wikitext(frame:expandTemplate{ title = "UnitDefinition", args = {			Name=args.Name,			Title=args.Title,			WikiName=frame:expandTemplate{ title = "UnitWikiName", args = { PAGENAME, enemy = "1" } },			Person=args.Person,			Origin=args.Origin,			TagID=enemyEID,			IntID=args.EnemyInternalID,			Gender=args.Gender,			WeaponType=args.WeaponType,			MoveType=args.MoveType,			Artist=args.artist,			ActorEN=table.concat(actors.EN,","),			ActorJP=table.concat(actors.JP,","),			AdditionDate=args.AdditionDate or args.ReleaseDate, -- TODO: Inaccruately uses release date of playable version (same behavior in old Template:Hero Infobox)			Properties=table.concat(enemyProps,","),			Description=args.description,			Appearance=table.concat(appearances,","),		} }) end if args.poolRarities and args.poolDate then -- define summoning availability local start = lang:formatDate( "Y-m-d", args.poolDate ).."T07:00:00Z" --frame:expandTemplate{ title = "SummoningAvailability", args = { rarity =, start = , end = } } if hasProperty["demoted_240"] then s:wikitext(frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = "4,5", start = start, ["end"] = "2018-04-10T06:59:59Z" } }				..frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = args.poolRarities, start = "2018-04-10T07:00:00Z"} }) elseif hasProperty["demoted_201904"] then s:wikitext(frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = "4,5", start = start, ["end"] ="2019-04-05T06:59:59Z" } }				..frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = args.poolRarities, start = "2019-04-05T07:00:00Z"} })

elseif hasProperty["demoted_202004"] then s:wikitext(frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = "4,5", start = start, ["end"] = "2020-04-08T06:59:59Z" } }				..frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = args.poolRarities, start = "2020-04-08T07:00:00Z"} }) elseif hasProperty["revivalOnly"] and hasProperty["removed_201904"] then s:wikitext(frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = args.poolRarities, start = start, ["end"] = "2019-04-10T06:59:59Z" } }				..frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = args.poolRarities, start = "2019-04-10T07:00:00Z", ["new heroes"] = false } }) elseif hasProperty["revivalOnly"] and hasProperty["removed_202008"] then s:wikitext(frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = args.poolRarities, start = start, ["end"] = "2020-08-02T06:59:59Z" } }				..frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = args.poolRarities, start = "2020-08-02T07:00:00Z", ["new heroes"] = false } }) else frame:expandTemplate{ title = "SummoningAvailability", args = { rarity = "4,5", start = start} } end end if args.randomStartTime then s:wikitext(frame:expandTemplate{ title = "RandomUnitDefinition", args = { unit = PAGENAME, startTime = args.randomStartTime, endTime= args.randomEndTime } }) end local imageList = nil if args.imageList then imageList = mw.text.split(args.imageList, ",") end

local tbl = mw.html.create("table") :addClass("wikitable") :addClass("hero-infobox") tbl:tag("tr"):tag("th") :attr("colspan", "2") :css("padding", "1em") :tag("span"):css("font-size","160%"):wikitext(args.Name or PAGENAME):done :tag("br"):done :tag("span"):css("font-size","90%"):wikitext(args.Title or ""):done

local function makeArtByHTMLNode(artist_name) local query = cargo.query( "Artists", "CONCAT(,NameUSEN,IF(Name!=NameUSEN,CONCAT(' (',Name,')'),),,IFNULL(CONCAT(' / ',Company,),''))=a", { where = "Name='"..escq(artist_name).."'" } )[1] local artistText if query then artistText = query.a		else artistText = ""..artist_name.."" end return mw.html.create("span"):css("font-size","85%"):wikitext("Art by: "..artistText) end local normalArt = Tab.tabber(ListUtil.map(imageList or COMMON_ART, function(face_type) return {INTERNAL_ART_NAMES_TO_NAMES[face_type] or face_type, ""} end)) if args.artist then normalArt = tostring(normalArt)..tostring(makeArtByHTMLNode(args.artist)) end local artCellContent if hasProperty["resplendent"] then s:wikitext(frame:expandTemplate{ title = "ResplendentHero", args = { unit = frame:expandTemplate{ title = "UnitWikiName", args = { PAGENAME } }, startTime = args.resplendentStartTime, endTime= args.resplendentEndTime, actorEN = args.resplendentActorEN, artist = args.resplendentArtist } }) local artCellContent = Tab.tabber { {"Normal", normalArt}, {"Resplendent", Tab.tabber(ListUtil.map(imageList or COMMON_ART, function(face_type) return {INTERNAL_ART_NAMES_TO_NAMES[face_type] or face_type, ""} end))}, }		if args.resplendentArtist then artCellContent = tostring(artCellContent)..tostring(makeArtByHTMLNode(args.resplendentArtist)) end else artCellContent = normalArt end tbl:tag("tr"):tag("td") :attr("colspan", "2") :css("text-align", "center") :wikitext(tostring(artCellContent)) local HEADER_CSS = "background-color:unset;border:none;color:unset;text-shadow:none;text-align:right;vertical-align:top;" local TD_CSS = "border:unset;border-left:2px #4b9acc solid;" local row row = tbl:tag("tr") row:tag("th"):cssText("width:10em;"..HEADER_CSS):wikitext("Description") row:tag("td"):cssText(TD_CSS):wikitext(args.description or "") if not isOnlyEnemy then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext(frame:expandTemplate{ title = "Hover", args = { "Rarities", "Represents the rarities at which the Hero can be summoned or rewarded. All Heroes can eventually reach 5★ rarity regardless of summoning rarities." } }) local rarityText = HeroUtil._availabilityText({PAGENAME}) if hasProperty["story"] then rarityText = rarityText.." — Story" end if hasProperty["special"] then rarityText = rarityText.." Focus — Special" end if hasProperty["legendary"] then rarityText = rarityText.." Focus — Legendary" end if hasProperty["mythic"] then rarityText = rarityText.." Focus — Mythic" end if hasProperty["ghb"] then rarityText = rarityText.." — Grand Hero Battle" end if hasProperty["tempest"] then rarityText = rarityText.." — Tempest Trials" end if hasProperty["prologue"] then rarityText = rarityText.." "..frame:expandTemplate{ title="Hover", args= {"*","Hero is obtained at 4★ during the Prologue." } }		end row:tag("td"):cssText(TD_CSS):wikitext(rarityText) end if hasProperty["legendary"] then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Effect") row:tag("td"):cssText(TD_CSS):wikitext(" "..(args.LegendaryEffect or "")) row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Ally Boost") local boosts = {} local template_args = { effect = args.LegendaryEffect, Duel = args.Duel } for _,stat in ipairs (STAT_TYPES) do			local arg_name = "Boost"..stat local n = args[arg_name] if n then boosts[#boosts+1] = "+"..n.." "..stat template_args[arg_name] = n			end end s:wikitext(frame:expandTemplate{ title = "LegendaryHero", args = template_args }) row:tag("td"):cssText(TD_CSS):wikitext(table.concat(boosts," ")) end if hasProperty["mythic"] then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Effect") row:tag("td"):cssText(TD_CSS):wikitext(" "..(args.MythicEffect or "")) row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Ally Boost")

local boosts = {} local template_args = { effect = args.MythicEffect, Duel = args.Duel } for _,stat in ipairs (STAT_TYPES) do			local arg_name = "Boost"..stat local n = args[arg_name] if n then boosts[#boosts+1] = "+"..n.." "..stat template_args[arg_name] = n			end end s:wikitext(frame:expandTemplate{ title = "MythicHero", args = template_args }) end if args.duo then s:wikitext(frame:expandTemplate{ title = "DuoHero", args = { duoSkill = args.duo, secondPerson = args.secondPerson, thirdPerson = args.thirdPerson, duel = args.Duel } })

row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Duo Skill") local cell = row:tag("td"):cssText(TD_CSS) cell:tag("div"):css("color","#528C34"):wikitext(args.duo) cell:wikitext("(Duo Skills can be used once per map by tapping the Duo button. Duo Skills cannot be used by units deployed using Pair Up.)") end if args.harmonized then s:wikitext(frame:expandTemplate{ title = "HarmonizedHero", args = { harmonizedSkill = args.harmonized, secondPerson = args.secondPerson, thirdPerson = args.thirdPerson, Duel = args.Duel } })

row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Harmonized Skill") local cell = row:tag("td"):cssText(TD_CSS) cell:tag("div"):css("color","#528C34"):wikitext(args.harmonized) cell:wikitext("(Harmonized Skills can be used by tapping the Harmonized button. Harmonized Skills cannot be used by units deployed using Pair Up.)") row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Resonance Effect") --TODO add manual parameter for overriding this text cell = row:tag("td"):cssText(TD_CSS) cell:wikitext(table.concat(ListUtil.map(entries, function(e) return games[e].shortName end)," / ")) cell:tag("br") cell:wikitext("Increases scores in Resonant Battles.") cell:tag("br") cell:wikitext("(More details can be found in the Help messages within Resonant Battles.)") end if args.Duel then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Standard Effect 1: Duel") row:tag("td"):cssText(TD_CSS):wikitext("If unit is 5★ and level 40 and unit's stats total less than "..args.Duel..", treats unit's stats as "..args.Duel.." in modes like Arena. (Higher-scoring opponents will appear. Stat total calculation excludes any values added by merges and skills.)") end if hasProperty["legendary"] and args.Duel then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Standard Effect 2: Pair Up") row:tag("td"):cssText(TD_CSS):wikitext("An ability that can only be used under certain circumstances. Pair Up can be accessed from the Interact with Allies menu, and allows this unit to join battle in a group with another ally.") end row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Weapon Type") row:tag("td"):cssText(TD_CSS):wikitext(args.WeaponType and frame:expandTemplate{title="WeaponTypeText",args={args.WeaponType}}) row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Move Type") row:tag("td"):cssText(TD_CSS):wikitext(args.MoveType and frame:expandTemplate{title="MoveText",args={args.MoveType}}) local function makeActorWikitext(actor, lang) if actor == "-" or actor == "—" then return "—" --TODO: Uncredited text elseif actor == "???" then return actor --TODO: Uncredited text elseif actor == "" then return actor else return ""..actor.."" end end if #actors.EN > 0 then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Voice Actor EN") -- TODO: make plural if number of actors > 1, mess around with lang:convertPlural ? row:tag("td"):cssText(TD_CSS):wikitext(table.concat(ListUtil.map(actors.EN, makeActorWikitext), args.actorSeparator or " + ")) end if args.resplendentActorEN then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Voice Actor EN (Resplendent Attire)") row:tag("td"):cssText(TD_CSS):wikitext(makeActorWikitext(args.resplendentActorEN)) end if #actors.JP > 0 then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext("Voice Actor JP") -- TODO: make plural if number of actors > 1, mess around with lang:convertPlural ? local eng_names = HashUtil.from_ipairs(cargo.query("VoiceActors","CONCAT('',Name,' (',NameJPJA,')')=vname,NameJPJA",{where="NameJPJA "..CargoUtil.make_sql_equality_check_string(actors.JP)}), function(t) return t.NameJPJA,t.vname end) row:tag("td"):cssText(TD_CSS):wikitext(table.concat(ListUtil.map(actors.JP, function(actor)			return eng_names[actor] or makeActorWikitext(actor)		end), args.actorSeparator or " + ")) end if args.releaseDate then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext(frame:expandTemplate{ title="Hover", args= {"Release Date","The date the Hero was first made obtainable." } }) local isoDate = lang:formatDate("Y-m-d",args.releaseDate) row:tag("td"):cssText(TD_CSS):tag("time"):attr("datetime",isoDate):wikitext(isoDate) end if args.additionDate and (args.additionDate ~= args.releaseDate) then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext(frame:expandTemplate{ title="Hover", args= {"Addition Date","The date the Hero was first present in the game." } }) if args.additionDate then local isoDate = lang:formatDate("Y-m-d",args.additionDate) row:tag("td"):cssText(TD_CSS):tag("time"):attr("datetime",isoDate):wikitext(isoDate) end end if not isOnlyEnemy then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext(frame:expandTemplate{ title="Hover", args= {"Entry","As shown on the Hero filter menu, and used in places like Limited Hero Battles." } }) local function attachEntryText(node, gameName) local gameData = games[gameName] --TODO: this currently assumes game name = game page name if gameData then node:wikitext(frame:expandTemplate{ title="EntryText", args= {gameData.value1} }) end return node end if #entries > 1 then local list = row:tag("td"):cssText(TD_CSS):tag("ul") for _,v in ipairs(entries) do				attachEntryText(list:tag("li"), v) 			end else attachEntryText(row:tag("td"):cssText(TD_CSS), entries[1]) end end if not ListUtil.equal(appearances,entries) then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext(frame:expandTemplate{ title="Hover", args= {"Appears in","As shown in the description. May not always be the first or last game the character appeared in." } }) if #appearances > 1 then local list = row:tag("td"):cssText(TD_CSS):tag("ul") for _,v in ipairs(appearances) do				list:tag("li"):wikitext(v) end else row:tag("td"):cssText(TD_CSS):wikitext(appearances[1] or "") end end if allyPID then row = tbl:tag("tr") local h = row:tag("th"):cssText(HEADER_CSS) if isEnemy then h:wikitext("Ally ") end h:wikitext("String ID") row:tag("td"):cssText(TD_CSS):tag("code"):wikitext(allyPID) end if args.InternalID then row = tbl:tag("tr") local h = row:tag("th"):cssText(HEADER_CSS) if isEnemy then h:wikitext("Ally ") end h:wikitext("Numeric ID") row:tag("td"):cssText(TD_CSS):tag("code"):wikitext(args.InternalID) end

if enemyEID or args.EnemyInternalID then row = tbl:tag("tr") local h = row:tag("th"):cssText(HEADER_CSS) if not isOnlyEnemy then h:wikitext("Enemy ") end h:wikitext("String ID") row:tag("td"):cssText(TD_CSS):tag("code"):wikitext(enemyEID) row = tbl:tag("tr") h = row:tag("th"):cssText(HEADER_CSS) if not isOnlyEnemy then h:wikitext("Enemy ") end h:wikitext("Numeric ID") row:tag("td"):cssText(TD_CSS):tag("code"):wikitext(args.EnemyInternalID) end if args.cv1 and args.cv2 then row = tbl:tag("tr") row:tag("th"):cssText(HEADER_CSS):wikitext(frame:expandTemplate{ title="Hover", args= {"Origin","The values used for 'Origin' for barracks and Catalog of Heroes. For more information, see the Catalog of Heroes page." } }) row:tag("td"):cssText(TD_CSS):tag("code"):wikitext(args.cv1..string.format("%010u",args.cv2)) --0000000000 end s:node(tbl) if mw.title.getCurrentTitle.namespace == 0 then local categoryWikiTextToAdd = {} categoryWikiTextToAdd[1] = "" if args.WeaponType then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" local query = cargo.query( "WeaponTypes", "Type,Color,CONCAT(Classes__full)=classes", { where = "WikiName='"..args.WeaponType.."'" } )[1] if query then local wptype = query.Type local typecountQuery = cargo.query( "WeaponTypes", "COUNT(DISTINCT WikiName)=weaponsInType", { where = "Type='"..escq(wptype).."'" } )[1] if typecountQuery and (tonumber(typecountQuery.weaponsInType) > 1) then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" for _,class in ipairs(mw.text.split(query.classes,",")) do					categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end end end if args.MoveType then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if isEnemy then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["story"] then --categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" TODO: come up with a better name, the meaning of a story hero does not correspond with the wiki usage for the story property end if hasProperty["special"] or hasProperty["specDisplay"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["legendary"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["mythic"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["duo"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["harmonized"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["resplendent"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["ghb"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end if hasProperty["tempest"] then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end for _,appearance in ipairs(appearances) do			local g = games[appearance] if g and g.name then categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" else categoryWikiTextToAdd[#categoryWikiTextToAdd+1] = "" end end -- The gender parameter should have no effect (gender is ambiguous and gender as determined by folder names may also be incorrect as with Libra: Fetching Friar) s:wikitext(table.concat(categoryWikiTextToAdd)) end

return s end

return p