Module:Util

From Fire Emblem Heroes Wiki
Jump to: navigation, search
Template-info.svg Documentation

This module blackboxes several FEH-related operations (eg. Fetching heroes, skills, skill info) and is intended for use by other Lua modules. This module should not be called from #invoke directly.

mf(str)[edit source]

Removes special characters from str so that it is usable as a file name; equivalent to {{MF}}.

isNilOrEmpty(val)[edit source]

Checks if the specified value is nil or empty

returnDefaultIfEmpty(val)[edit source]

Returns '-' if the specified value is nil or empty

parseTime(str)[edit source]

Returns the Unix timestamp representing the given ISO 8601 formatted time.

formatTime(t)[edit source]

Returns the ISO 8601 formatted time represented by the given Unix timestamp.

MIN_TIME[edit source]

The constant string 1970-01-01T00:00:00Z.

MAX_TIME[edit source]

The constant string 2038-01-19T03:14:07Z.

getHeroIcon(hero,size)[edit source]

  • Input: Hero name and image size
  • Output: Icon of the Hero that links to the Hero page

getWeaponSortOrder()[edit source]

Obtains the sort order of weapon types, by color then by weapon. Uses WeaponTypes.

Return values[edit source]

A table that maps full weapon types to increasing values, e.g. 'Red Sword'1, 'Red Bow'2.

getMoveSortOrder()[edit source]

Obtains the sort order of move types. Uses MoveTypes.

Return values[edit source]

A table that maps move types to increasing values, e.g. 'Infantry'1, 'Armored'2.

getOriginSortOrder()[edit source]

Obtains the sort order of game titles. Uses the values from GameSortValue.

Return values[edit source]

A table that maps game titles to sort values, e.g. 'Fire Emblem Heroes'0, 'Fire Emblem: Shadow Dragon and the Blade of Light'1, 'Fire Emblem: Mystery of the Emblem'1.

getSkillChains(skills)[edit source]

Returns two tables representing dependencies between the given skills. The first table contains WikiName of skills as keys and lists of the skill's prerequisites as values; the second table is for descendants of skills.

If a table is passed as an argument, it must be a list that contains only the WikiNames of skills to be queried. Otherwise all existing skills are used.

getHeroAvailability(args)[edit source]

Obtains the non-focus rarities, focus rarities, and reward rarities of a Hero or all Heroes. Focus and reward rarities are based on all Summoning Focuses and rewards over time. args should be a Lua table with the following named arguments:

  • page: The page name of the Hero.
  • current: True if only the current general summoning pool is used to populate non-focus rarities, false to use all past general summoning pools as well.
  • new: True if only Heroes that appear in New Heroes or Special Heroes summoning events are considered, false to include all summoning events. Has no effect if page is given.
  • mask: If given, must be a table with the following keys:
    • nonfocus: Query non-focus rarities if this field is true, skip the query otherwise.
    • focus: Query focus rarities if this field is true, skip the query otherwise.
    • reward: Query reward rarities if this field is true, skip the query otherwise.

Return values[edit source]

The availability object is a Lua table with the following fields:

  • nonfocus: Lua sequence of rarities at which a Hero can be summoned as a non-focus unit. Unavailable if omitted using the mask parameter; otherwise, always present even if it is empty.
  • focus: Lua sequence of rarities at which a Hero can be summoned as a focus unit. Similarly masked.
  • reward: Lua sequence of rarities at which a Hero has been rewarded. Similarly masked.

And the following method:

  • bounds(): Returns the minimum and maximum rarities the Hero is available at, based on the rarities present in the object. Returns nil if the Hero is unavailable.

If page is given, the return value is simply the availability object of the Hero with that page name.

If page is nil, the function returns a table instead, where each key is a Hero's page name and the value is the availability object of that Hero. Only Heroes with available rarities appear in this table (e.g. Special Heroes will not appear at all if mask is set to {nonfocus = true}, because they never appear as non-focus units in summoning events).

getRarityText()[edit source]

Returns an array containing rarity texts from 1★ to 5★.

getDifficulties()[edit source]

Returns a sorted array of difficulties.

difficultySort(x, y)[edit source]

Returns the relative order between two unit tab names, based on their difficulties. Can be used as the comparison function to table.sort.

-- This module blackboxes several FEH-related operations (eg. Fetching heroes, skills, skill info)
-- and is intended for use by other Lua modules. 
-- This module should not be called from #invoke directly.

local p = {}
local mw = mw
local cargo = mw.ext.cargo
local List = require 'Module:ListUtil'
local Hash = require 'Module:HashUtil'
local escq = require 'Module:EscQ'.main1
local mf = require('Module:MF').main1

p.mf = mf
p.mf2 = require 'Module:MF2'.main1

----------------------------------
----------------------------------
-- Hero-related functions
----------------------------------
----------------------------------

p.getHeroIcon = function (hero, size)
  return ('[[File:%s_Face_FC.webp|%s|link=%s]]'):format(mf(hero), size, hero)
end

----------------------------------
----------------------------------
-- Sort-related functions
----------------------------------
----------------------------------

p.getWeaponSortOrder = function ()
  return Hash.invert(List.map_self(cargo.query('WeaponTypes', 'WikiName', {
    groupBy = '_pageName',
    orderBy = 'ColorSort,Sort',
    limit = 50,
  }), function (x) return x.WikiName end))
end

p.getMoveSortOrder = function ()
  return Hash.invert(List.map_self(cargo.query('MoveTypes', 'WikiName', {
    groupBy = '_pageName',
    orderBy = 'Sort',
    limit = 50,
  }), function (x) return x.WikiName end))
end

p.getOriginSortOrder = function ()
  local results = cargo.query('GameSortValue', '_pageName,value1', {
    groupBy = '_pageName',
    limit = 50,
  })
  return Hash.from_pairs(results, function (x)
    return x._pageName, tonumber(x.value1)
  end)
end

p.getSkillChains = function (skills)
  local query = nil
  if skills == nil then
    query = cargo.query('Skills,Skills__Required', 'Skills.WikiName=NextSkill,Skills__Required._value=Prev', {
      join = 'Skills._ID=Skills__Required._rowID',
      groupBy = 'NextSkill,Prev',
      limit = 5000,
    })
    skills = List.map(query, function (v) return v.NextSkill end)
  elseif #skills == 0 then
    return {}, {}
  else
    query = cargo.query('Skills,Skills__Required', 'Skills.WikiName=NextSkill,Skills__Required._value=Prev', {
      join = 'Skills__Required._rowID=Skills._ID',
      groupBy = 'NextSkill,Prev',
      where = ("WikiName IN (%s)"):format(table.concat(List.map(skills, function (v) return ("'%s'"):format(escq(v)) end), ', ')),
      limit = 5000,
    })
  end
  local prereq = Hash.generate(skills, function () return {} end)
  local after = Hash.generate(skills, function () return {} end)
  for _, v in ipairs(query) do
    if v.Prev ~= '' then
      local p = prereq[v.NextSkill]
      if p then
        p[#p + 1] = v.Prev
      end
      local n = after[v.Prev]
      if n then
        n[#n + 1] = v.NextSkill
      end
    end
  end
  return prereq, after
end

p.getHeroAvailability = function (args)
  args = args or {}
  local useNonfocus = not (args.mask and not args.mask.nonfocus)
  local useFocus = not (args.mask and not args.mask.focus)
  local useReward = not (args.mask and not args.mask.reward)
  local unavailable = function ()
    return {
      nonfocus = useNonfocus and {} or nil,
      focus = useFocus and {} or nil,
      reward = useReward and {} or nil,
      bounds = function (self)
        local r = {}
        if self.nonfocus then
          List.concat_self(r, self.nonfocus)
        end
        if self.focus then
          List.concat_self(r, self.focus)
        end
        if self.reward then
          List.concat_self(r, self.reward)
        end
        return List.minmax(r)
      end,
    }
  end
  local ret = {}

  if useNonfocus then
    local nonfocusQuery = cargo.query('SummoningAvailability,Units=U', 'U._pageName=page,Rarity', {
      join = 'SummoningAvailability._pageName=U._pageName',
      where = (args.page and ("U._pageName='%s'"):format(escq(args.page)) or 'U._pageName IS NOT NULL') ..
        " AND IFNULL(Properties__full,'') NOT LIKE '%enemy%' AND Rarity IS NOT NULL" ..
        (args.current and ' AND (NOW() BETWEEN StartTime AND EndTime)' or '') ..
        (args.new and ' AND NewHeroes' or ''),
      groupBy = 'page,Rarity',
      orderBy = 'page,Rarity',
      limit = 5000,
    })
    for page, vs in pairs(List.group_by(nonfocusQuery, function (v) return v.page end)) do
      ret[page] = unavailable()
      for _, v in ipairs(vs) do
        table.insert(ret[page].nonfocus, tonumber(v.Rarity))
      end
    end
  end

  if useFocus then
    local focusQuery = cargo.query('SummoningFocusUnits=SFU,Units=U', 'U._pageName=page,Rarity', {
      join = 'SFU.Unit=U.WikiName',
      where = 'U.WikiName IS NOT NULL AND Rarity IS NOT NULL' .. (args.page and (" AND U._pageName='%s'"):format(escq(args.page)) or ''),
      groupBy = 'page,Rarity',
      orderBy = 'page,Rarity',
      limit = 5000,
    })
    for page, vs in pairs(List.group_by(focusQuery, function (v) return v.page end)) do
      ret[page] = ret[page] or unavailable()
      for _, v in ipairs(vs) do
        table.insert(ret[page].focus, tonumber(v.Rarity))
      end
    end
  end

  if useReward then
    local rewardQuery = cargo.query('Distributions,Units=U', 'U._pageName=page,Rarity', {
      join = 'Distributions.Unit=U._pageName',
      where = (args.page and ("U._pageName='%s'"):format(escq(args.page)) or 'U._pageName IS NOT NULL') ..
        " AND IFNULL(Properties__full,'') NOT LIKE '%enemy%' AND Rarity IS NOT NULL",
      groupBy = 'page,Rarity',
      orderBy = 'page,Rarity',
      limit = 5000,
    })
    for page, vs in pairs(List.group_by(rewardQuery, function (v) return v.page end)) do
      ret[page] = ret[page] or unavailable()
      for _, v in ipairs(vs) do
        table.insert(ret[page].reward, tonumber(v.Rarity))
      end
    end
  end

  return args.page and (ret[args.page] or unavailable()) or ret
end

----------------------------------
----------------------------------
-- Misc. General functions
----------------------------------
----------------------------------

-- Checks if the specified value is nil or empty
p.isNilOrEmpty = function (val)
    return val == nil or mw.text.trim(val) == ''
end

-- Returns '-' if the specified value is nil or empty
p.returnDefaultIfEmpty = function (val)
  return p.isNilOrEmpty(val) and '-' or val
end

p.parseTime = function (str)
  local y, m, d, hh, mm, ss = mw.ustring.match(str, '^(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z$')
  if ss then
    return os.time {year = y, month = m, day = d, hour = hh, min = mm, sec = ss}
  end
end

p.formatTime = function (t)
  return os.date('%Y-%m-%dT%H:%M:%SZ', t)
end

p.MIN_TIME = p.formatTime(0)
p.MAX_TIME = p.formatTime(0x7FFFFFFF)

p.getRarityTexts = function ()
  local frame = mw.getCurrentFrame()
  local z = {
    frame:expandTemplate{title = 'Rarity', args = {1}},
    frame:expandTemplate{title = 'Rarity', args = {2}},
    frame:expandTemplate{title = 'Rarity', args = {3}},
    frame:expandTemplate{title = 'Rarity', args = {4}},
    frame:expandTemplate{title = 'Rarity', args = {5}},
--    S = frame:expandTemplate{title = 'Rarity', args = {'S'}},
  }
  for i, v in ipairs(z) do
    z[tostring(i)] = v
  end
  return z
end

local DIFFICULTIES = {'Normal', 'Hard', 'Lunatic', 'Infernal', 'Abyssal'}

p.getDifficulties = function ()
	return Hash.clone(DIFFICULTIES)
end

p.difficultySort = function (x, y)
	local i1 = List.find_if(DIFFICULTIES, function (dif) return mw.ustring.find(x, dif, 1, true) end) or #DIFFICULTIES + 1
	local i2 = List.find_if(DIFFICULTIES, function (dif) return mw.ustring.find(y, dif, 1, true) end) or #DIFFICULTIES + 1
	if i1 < i2 then
		return true
	elseif i2 < i1 then
		return false
	end

	local lv1 = tonumber(mw.ustring.match(x, '^[^/]+/LV.(%d+)/%d+ battles$')) or math.huge
	local lv2 = tonumber(mw.ustring.match(y, '^[^/]+/LV.(%d+)/%d+ battles$')) or math.huge
	return lv1 < lv2 or (lv1 == lv2 and x < y)
end

return p