From Arms of God Wiki

bot: operator iteration 2026-06-10d (hover tooltips / filter chips + DPS sort / damage-type hubs / stat reverse-lookup pages)
 
bot: render-time derivation refactor (phase 2)
Line 1: Line 1:
-- Module:StatIndex — stat reverse-lookup renderer (Arms of God).
-- Module:StatIndex — render-time stat reverse-lookup (Arms of God).
-- Reads Data:StatIndex.json (built by the flatten step):
-- Scans the stat-bearing source Data:<Category>.json pages at render time.
--  stats[key]          -> every hero / blessing / upgrade / passive / Crux
-- No Data:StatIndex.json.
--                          power modifying the stat (desc by amount)
--
--  element_weapons[key] -> every weapon dealing that elemental damage type
--  {{#invoke:StatIndex|boosters|<stat>}} -> every hero / blessing /
-- {{#invoke:StatIndex|boosters|<stat>}} -> 'Boosted by' sortable table
--      upgrade / passive / Crux power modifying the stat (desc by amount)
-- {{#invoke:StatIndex|weapons|<stat>}}  -> 'Dealt by' sortable table
--  {{#invoke:StatIndex|weapons|<element>}} -> every weapon dealing the
-- Source cells are .wm-tip wrapped (Common.js hover-infobox popup).
--       elemental damage type (desc by amount)
local Core = require('Module:Core')
local p = {}
local p = {}


Line 12: Line 13:
local TH  = '! style="background:var(--table-header-bg, #26272e);color:var(--infobox-header-fg, #f1e9d2);" | '
local TH  = '! style="background:var(--table-header-bg, #26272e);color:var(--infobox-header-fg, #f1e9d2);" | '


-- SC-01: canonicalise the frozen mw.loadJsonData table (its `#` is 0).
-- Same mechanic, two data names: fold 'Explosive Range' into the
local function fix(t)
-- 'Explosive Radius' lookup key (and vice versa when querying either).
   if type(t) ~= 'table' then return t end
local ALIAS_GROUPS = {
  ['Explosive Radius'] = {'Explosive Radius', 'Explosive Range'},
  ['Explosive Range'] = {'Explosive Radius', 'Explosive Range'},
}
 
local MODIFIER_CATS = {'Characters', 'Blessings', 'Upgrades', 'Passives', 'Crux'}
 
local function statKeysFor(query)
   return ALIAS_GROUPS[query] or {query}
end
 
-- Internal: list of booster entries for a stat key. {rec, cat, tier, value, sort}
function p._boosters(key)
  local keys = statKeysFor(key)
   local out = {}
   local out = {}
   for k, v in pairs(t) do out[k] = fix(v) end
   for _, cat in ipairs(MODIFIER_CATS) do
    for _, rec in ipairs(Core.load(cat)) do
      local st = rec.stats or {}
      for _, k in ipairs(keys) do
        local v = st[k]
        if v ~= nil then
          local val = Core.fmtStat(v, true)
          if val ~= nil then
            local tier = rec.tier or rec.type or ''
            out[#out + 1] = {
              rec = rec, cat = cat, tier = tostring(tier),
              value = val, sort = Core.numStat(v) or 0,
            }
          end
        end
      end
    end
  end
  table.sort(out, function(a, b)
    if a.sort ~= b.sort then return a.sort > b.sort end
    return mw.ustring.lower(a.rec.name or '') < mw.ustring.lower(b.rec.name or '')
  end)
   return out
   return out
end
end


local function loadData()
-- Internal: list of weapons dealing an elemental damage type.
   local ok, data = pcall(mw.loadJsonData, 'Data:StatIndex.json')
function p._weapons(key)
  if not ok or type(data) ~= 'table' then return nil end
   local out = {}
  return data
  for _, rec in ipairs(Core.load('Weapons')) do
end
    local v = Core.numStat((rec.stats or {})[key])
 
    if v ~= nil and v > 0 and v < 1000 then
local function tipcell(e)
      out[#out + 1] = {
  local label
        rec = rec, class = Core.weaponClass(rec),
  if e.icon and e.icon ~= '' then
        tier = tostring(rec.tier or ''), value = Core.fmtNum(v), sort = v,
    label = '[[File:' .. e.icon .. '|24px|link=' .. e.slug .. ']] [['
      }
      .. e.slug .. '|' .. e.name .. ']]'
     end
  else
     label = '[[' .. e.slug .. '|' .. e.name .. ']]'
   end
   end
   return '<span class="wm-tip" data-tip-title="' .. e.slug .. '">'
   table.sort(out, function(a, b)
    .. label .. '</span>'
    if a.sort ~= b.sort then return a.sort > b.sort end
    return mw.ustring.lower(a.rec.name or '') < mw.ustring.lower(b.rec.name or '')
  end)
  return out
end
end


Line 45: Line 81:
function p.boosters(frame)
function p.boosters(frame)
   local key = arg1(frame)
   local key = arg1(frame)
  local data = loadData()
   local list = p._boosters(key)
   local list = data and data.stats and data.stats[key]
   if #list == 0 then
   if not list then
     return "''No heroes, blessings, upgrades, passives or Crux powers " ..
     return "''No heroes, blessings, upgrades, passives or Crux powers " ..
       "modify this stat in the current game data.''"
       "modify this stat in the current game data.''"
   end
   end
  list = fix(list)
   local out = { TBL, '|-',
   local out = { TBL, '|-',
     TH .. 'Source', TH .. 'Category', TH .. 'Tier / Type', TH .. 'Amount' }
     TH .. 'Source', TH .. 'Category', TH .. 'Tier / Type', TH .. 'Amount' }
   for _, e in ipairs(list) do
   for _, e in ipairs(list) do
     out[#out+1] = '|-'
     out[#out+1] = '|-'
     out[#out+1] = '| ' .. tipcell(e)
     out[#out+1] = '| ' .. Core.iconLink(e.rec, 24)
     out[#out+1] = '| ' .. (e.cat or '')
     out[#out+1] = '| ' .. e.cat
     out[#out+1] = '| ' .. ((e.tier ~= nil and e.tier ~= '') and e.tier or '—')
     out[#out+1] = '| ' .. ((e.tier ~= '') and e.tier or '—')
     out[#out+1] = '| data-sort-value="' .. tostring(e.sort or 0) .. '" | '
     out[#out+1] = '| data-sort-value="' .. tostring(e.sort) .. '" | ' .. e.value
      .. (e.value or '')
   end
   end
   out[#out+1] = '|}'
   out[#out+1] = '|}'
Line 68: Line 101:
function p.weapons(frame)
function p.weapons(frame)
   local key = arg1(frame)
   local key = arg1(frame)
  local data = loadData()
   local list = p._weapons(key)
   local list = data and data.element_weapons and data.element_weapons[key]
   if #list == 0 then
   if not list then
     return "''No weapons deal this damage type in the current game data.''"
     return "''No weapons deal this damage type in the current game data.''"
   end
   end
  list = fix(list)
   local out = { TBL, '|-',
   local out = { TBL, '|-',
     TH .. 'Weapon', TH .. 'Class', TH .. 'Tier', TH .. key }
     TH .. 'Weapon', TH .. 'Class', TH .. 'Tier', TH .. key }
   for _, e in ipairs(list) do
   for _, e in ipairs(list) do
     out[#out+1] = '|-'
     out[#out+1] = '|-'
     out[#out+1] = '| ' .. tipcell(e)
     out[#out+1] = '| ' .. Core.iconLink(e.rec, 24)
     out[#out+1] = '| ' .. (e['class'] or '')
     out[#out+1] = '| ' .. e.class
     out[#out+1] = '| ' .. (e.tier or '')
     out[#out+1] = '| ' .. e.tier
     out[#out+1] = '| data-sort-value="' .. tostring(e.sort or 0) .. '" | '
     out[#out+1] = '| data-sort-value="' .. tostring(e.sort) .. '" | ' .. e.value
      .. (e.value or '')
   end
   end
   out[#out+1] = '|}'
   out[#out+1] = '|}'

Revision as of 08:30, 10 June 2026

Documentation for this module may be created at Module:StatIndex/doc

-- Module:StatIndex — render-time stat reverse-lookup (Arms of God).
-- Scans the stat-bearing source Data:<Category>.json pages at render time.
-- No Data:StatIndex.json.
--
--   {{#invoke:StatIndex|boosters|<stat>}} -> every hero / blessing /
--       upgrade / passive / Crux power modifying the stat (desc by amount)
--   {{#invoke:StatIndex|weapons|<element>}} -> every weapon dealing the
--       elemental damage type (desc by amount)
local Core = require('Module:Core')
local p = {}

local TBL = '{| class="wikitable sortable" style="font-size:0.95em;background:var(--table-row-odd, #1b1c20);color:var(--table-text, #e6e6e6);border-color:var(--table-border, #3a3c44);"'
local TH  = '! style="background:var(--table-header-bg, #26272e);color:var(--infobox-header-fg, #f1e9d2);" | '

-- Same mechanic, two data names: fold 'Explosive Range' into the
-- 'Explosive Radius' lookup key (and vice versa when querying either).
local ALIAS_GROUPS = {
  ['Explosive Radius'] = {'Explosive Radius', 'Explosive Range'},
  ['Explosive Range'] = {'Explosive Radius', 'Explosive Range'},
}

local MODIFIER_CATS = {'Characters', 'Blessings', 'Upgrades', 'Passives', 'Crux'}

local function statKeysFor(query)
  return ALIAS_GROUPS[query] or {query}
end

-- Internal: list of booster entries for a stat key. {rec, cat, tier, value, sort}
function p._boosters(key)
  local keys = statKeysFor(key)
  local out = {}
  for _, cat in ipairs(MODIFIER_CATS) do
    for _, rec in ipairs(Core.load(cat)) do
      local st = rec.stats or {}
      for _, k in ipairs(keys) do
        local v = st[k]
        if v ~= nil then
          local val = Core.fmtStat(v, true)
          if val ~= nil then
            local tier = rec.tier or rec.type or ''
            out[#out + 1] = {
              rec = rec, cat = cat, tier = tostring(tier),
              value = val, sort = Core.numStat(v) or 0,
            }
          end
        end
      end
    end
  end
  table.sort(out, function(a, b)
    if a.sort ~= b.sort then return a.sort > b.sort end
    return mw.ustring.lower(a.rec.name or '') < mw.ustring.lower(b.rec.name or '')
  end)
  return out
end

-- Internal: list of weapons dealing an elemental damage type.
function p._weapons(key)
  local out = {}
  for _, rec in ipairs(Core.load('Weapons')) do
    local v = Core.numStat((rec.stats or {})[key])
    if v ~= nil and v > 0 and v < 1000 then
      out[#out + 1] = {
        rec = rec, class = Core.weaponClass(rec),
        tier = tostring(rec.tier or ''), value = Core.fmtNum(v), sort = v,
      }
    end
  end
  table.sort(out, function(a, b)
    if a.sort ~= b.sort then return a.sort > b.sort end
    return mw.ustring.lower(a.rec.name or '') < mw.ustring.lower(b.rec.name or '')
  end)
  return out
end

local function arg1(frame)
  local k = frame.args[1] or ''
  return (k:gsub('^%s+', ''):gsub('%s+$', ''))
end

function p.boosters(frame)
  local key = arg1(frame)
  local list = p._boosters(key)
  if #list == 0 then
    return "''No heroes, blessings, upgrades, passives or Crux powers " ..
      "modify this stat in the current game data.''"
  end
  local out = { TBL, '|-',
    TH .. 'Source', TH .. 'Category', TH .. 'Tier / Type', TH .. 'Amount' }
  for _, e in ipairs(list) do
    out[#out+1] = '|-'
    out[#out+1] = '| ' .. Core.iconLink(e.rec, 24)
    out[#out+1] = '| ' .. e.cat
    out[#out+1] = '| ' .. ((e.tier ~= '') and e.tier or '—')
    out[#out+1] = '| data-sort-value="' .. tostring(e.sort) .. '" | ' .. e.value
  end
  out[#out+1] = '|}'
  return table.concat(out, '\n')
end

function p.weapons(frame)
  local key = arg1(frame)
  local list = p._weapons(key)
  if #list == 0 then
    return "''No weapons deal this damage type in the current game data.''"
  end
  local out = { TBL, '|-',
    TH .. 'Weapon', TH .. 'Class', TH .. 'Tier', TH .. key }
  for _, e in ipairs(list) do
    out[#out+1] = '|-'
    out[#out+1] = '| ' .. Core.iconLink(e.rec, 24)
    out[#out+1] = '| ' .. e.class
    out[#out+1] = '| ' .. e.tier
    out[#out+1] = '| data-sort-value="' .. tostring(e.sort) .. '" | ' .. e.value
  end
  out[#out+1] = '|}'
  return table.concat(out, '\n')
end

return p