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: documentation pass — editor-facing docs (data descriptions, module comments, template usage, Help rewrite)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
-- Module:StatIndex — stat reverse-lookup renderer (Arms of God).
-- Module:StatIndex — "what modifies this stat?" reverse lookups.
-- Reads Data:StatIndex.json (built by the flatten step):
--
--  stats[key]          -> every hero / blessing / upgrade / passive / Crux
-- WHAT IT DOES
--                          power modifying the stat (desc by amount)
--  Renders the lookup tables on the per-stat pages (Armor, Speed, ...)
--  element_weapons[key] -> every weapon dealing that elemental damage type
--  and the damage-type hubs (Holy/Fire/Electric Damage). Nothing is
-- {{#invoke:StatIndex|boosters|<stat>}}  -> 'Boosted by' sortable table
--   stored: it scans the stat-bearing source Data pages at render time,
-- {{#invoke:StatIndex|weapons|<stat>}}   -> 'Dealt by' sortable table
--  so a balance change edited into a Data page updates every stat page
-- Source cells are .wm-tip wrapped (Common.js hover-infobox popup).
--  on purge.
--
-- HOW TO INVOKE
--  {{#invoke:StatIndex|boosters|Armor}}      every hero / blessing /
--      upgrade / passive / Crux power modifying the stat (desc by amount)
--  {{#invoke:StatIndex|weapons|Fire Damage}}  every weapon dealing the
--      elemental damage type (desc by amount)
--
-- SOURCE DATA IT READS (via Module:Core)
--   boosters: Data:Characters.json, Data:Blessings.json,
--  Data:Upgrades.json, Data:Passives.json, Data:Crux.json.
--   weapons: Data:Weapons.json.
local Core = require('Module:Core')
local p = {}
local p = {}


Line 12: Line 24:
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 92:
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 112:
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] = '|}'

Latest revision as of 16:04, 10 June 2026

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

-- Module:StatIndex — "what modifies this stat?" reverse lookups.
--
-- WHAT IT DOES
--   Renders the lookup tables on the per-stat pages (Armor, Speed, ...)
--   and the damage-type hubs (Holy/Fire/Electric Damage). Nothing is
--   stored: it scans the stat-bearing source Data pages at render time,
--   so a balance change edited into a Data page updates every stat page
--   on purge.
--
-- HOW TO INVOKE
--   {{#invoke:StatIndex|boosters|Armor}}       every hero / blessing /
--       upgrade / passive / Crux power modifying the stat (desc by amount)
--   {{#invoke:StatIndex|weapons|Fire Damage}}  every weapon dealing the
--       elemental damage type (desc by amount)
--
-- SOURCE DATA IT READS (via Module:Core)
--   boosters: Data:Characters.json, Data:Blessings.json,
--   Data:Upgrades.json, Data:Passives.json, Data:Crux.json.
--   weapons: Data:Weapons.json.
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