Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Join the Playtest on Steam Now: SpiritVale

Module:GameSummons

Module:GameSummons renders summon data from Data:summons.json into a reusable infobox-style table.

It is intended to be used via a template (for example Template:Summon) so that summons can be embedded on any page without creating individual pages for each summon.

This module:

  • Loads data via Module:GameDataGameData.loadSummons().
  • Looks up summons primarily by display "Name" (what editors use), with "Internal Name" as an optional fallback.
  • Builds a table with only the fields that actually exist for that summon.

Data source

Summon data comes from Data:summons.json, which is a JSON page with this top-level structure (see Module:GameData/doc for full details):

{
  "version": "SpiritVale-0.9.3",
  "schema_version": 1,
  "generated_at": "2025-12-12T17:24:05.818161+00:00",
  "records": [
    {
      "Name": "Angel Mage",
      "Internal Name": "Angel Mage",
      "Skill Name": "Summon Angel",
      "Skill Internal Name": "SummonAngel",
      "Attributes": [ ... ],
      "Skills": [ ... ],
      "Stages": [ ... ]
    }
  ]
}

Each record is a single summon. Important keys:

  • "Name" – the display name (what players and editors will usually see and use).
  • "Internal Name" – the stable ID used internally and available as an optional parameter for power users and tooling.
  • "Skill Name" – the name of the summoning skill.
  • "Skill Internal Name" – the internal ID of the summoning skill.
  • "Attributes" – a list of attribute blocks (Strength, Agility, etc., with Base and Per Level values where present).
  • "Skills" – a list of active skills the summon can use.
  • "Stages" – evolution stages, with Unlock Level and monster names.

Output

For a given summon, the module renders a table with the CSS class spiritvale-summon-infobox.

Depending on what exists in the JSON record, the table may include:

  • Header row with summon name.
  • Internal name (for debugging / internal reference).
  • Summon skill:
    • Display skill name, and internal ID if present.
  • Attributes:
    • One line per attribute from "Attributes", usually in the form:
      • Strength – Base 5.0, 0.3 / Lv
      • Health Points – Base 100.0
  • Skills:
    • One line per entry from "Skills", including:
      • Skill name
      • Level
      • Cooldown
      • Cast Time (where present)
      • Target Health (where present)
  • Stages:
    • One line per entry from "Stages", usually in the form:
      • Stage 1: Angel (Unlock Lv.1)
      • Stage 2: Archangel (Unlock Lv.25)
      • Stage 3: Angel Mage (Unlock Lv.50)

Rows are only shown if the underlying field exists in the JSON for that summon.


Public interface

The module exposes a single entry point for templates:

GameSummons.infobox(frame)

This is usually called via #invoke from a template, not directly from pages.

It accepts the following parameters (either passed directly or via a wrapper template):

  • 1 – unnamed parameter; treated as the summon "Name".
  • name – explicit display "Name" of the summon (equivalent to 1).
  • id"Internal Name" of the summon (optional fallback / power use).

Lookup order:

  1. If name or the first unnamed parameter is provided and matches a record’s "Name", that record is used.
  2. Otherwise, if id is provided and matches an "Internal Name", that record is used.
  3. If nothing is found, a small error message is returned and the page is categorized for tracking.

Example direct usage (not recommended; normally use a template):

Unknown summon: ?

or:

Unknown summon: ?

Template:Summon

The recommended way to use this module is via a small wrapper template, for example:

Template:Summon
 Unknown summon: ?

Typical usage on any page:

Angel Mage
Internal nameAngel Mage
Summon skillSummon Angel (SummonAngel)
AttributesStrength – Base 5.0, 0.3 / Lv
Agility – Base 5.0, 0.3 / Lv
Vitality – Base 10.0, 0.6 / Lv
Intelligence – Base 10.0, 0.6 / Lv
Dexterity – Base 10.0, 0.6 / Lv
Luck – Base 5.0, 0.3 / Lv
Health Points – Base 100.0
SkillsHeal (Lv.1, CD 5 s, CT 1.5 s, Target HP 0.80)
Holy Light (Lv.3, CD 5 s, CT 1.5 s, Target HP 1.00)
Dissonance Well (Lv.3, CD 10 s, CT 1.5 s, Target HP 1.00)
Resonance Well (Lv.3, CD 10 s, CT 1.5 s, Target HP 0.80)
Sacred Aegis (Lv.2, CD 5 s, CT 1.5 s, Target HP 1.00)
StagesStage 1: Angel (Unlock Lv.1)
Stage 2: Archangel (Unlock Lv.25)
Stage 3: Angel Mage (Unlock Lv.50)

or, explicitly:

Angel Mage
Internal nameAngel Mage
Summon skillSummon Angel (SummonAngel)
AttributesStrength – Base 5.0, 0.3 / Lv
Agility – Base 5.0, 0.3 / Lv
Vitality – Base 10.0, 0.6 / Lv
Intelligence – Base 10.0, 0.6 / Lv
Dexterity – Base 10.0, 0.6 / Lv
Luck – Base 5.0, 0.3 / Lv
Health Points – Base 100.0
SkillsHeal (Lv.1, CD 5 s, CT 1.5 s, Target HP 0.80)
Holy Light (Lv.3, CD 5 s, CT 1.5 s, Target HP 1.00)
Dissonance Well (Lv.3, CD 10 s, CT 1.5 s, Target HP 1.00)
Resonance Well (Lv.3, CD 10 s, CT 1.5 s, Target HP 0.80)
Sacred Aegis (Lv.2, CD 5 s, CT 1.5 s, Target HP 1.00)
StagesStage 1: Angel (Unlock Lv.1)
Stage 2: Archangel (Unlock Lv.25)
Stage 3: Angel Mage (Unlock Lv.50)

Internal IDs can still be used when needed:

Angel Mage
Internal nameAngel Mage
Summon skillSummon Angel (SummonAngel)
AttributesStrength – Base 5.0, 0.3 / Lv
Agility – Base 5.0, 0.3 / Lv
Vitality – Base 10.0, 0.6 / Lv
Intelligence – Base 10.0, 0.6 / Lv
Dexterity – Base 10.0, 0.6 / Lv
Luck – Base 5.0, 0.3 / Lv
Health Points – Base 100.0
SkillsHeal (Lv.1, CD 5 s, CT 1.5 s, Target HP 0.80)
Holy Light (Lv.3, CD 5 s, CT 1.5 s, Target HP 1.00)
Dissonance Well (Lv.3, CD 10 s, CT 1.5 s, Target HP 1.00)
Resonance Well (Lv.3, CD 10 s, CT 1.5 s, Target HP 0.80)
Sacred Aegis (Lv.2, CD 5 s, CT 1.5 s, Target HP 1.00)
StagesStage 1: Angel (Unlock Lv.1)
Stage 2: Archangel (Unlock Lv.25)
Stage 3: Angel Mage (Unlock Lv.50)

This keeps page wikitext simple while centralizing all JSON loading and formatting logic inside Lua.


-- Module:GameSummons
--
-- Renders summon data (from Data:summons.json) into an infobox-style table.
-- Data is loaded via Module:GameData.
--
-- Supported usage patterns (via Template:Summon):
--   {{Summon|Angel Mage}}              -> uses display Name (recommended)
--   {{Summon|name=Angel Mage}}         -> explicit Name
--   {{Summon|id=Angel Mage}}           -> Internal Name (power use)

local GameData = require("Module:GameData")

local p = {}

----------------------------------------------------------------------
-- Internal helpers
----------------------------------------------------------------------

local summonsCache

local function getSummons()
    if not summonsCache then
        summonsCache = GameData.loadSummons()
    end
    return summonsCache
end

local function getArgs(frame)
    local parent = frame:getParent()
    if parent then
        return parent.args
    end
    return frame.args
end

local function listToText(list, sep)
    if type(list) ~= "table" or #list == 0 then
        return nil
    end
    return table.concat(list, sep or ", ")
end

local function addRow(tbl, label, value)
    if value == nil or value == "" then
        return
    end
    local row = tbl:tag("tr")
    row:tag("th"):wikitext(label):done()
    row:tag("td"):wikitext(value):done()
end

-- Lookup by Internal Name
local function getSummonById(id)
    if not id or id == "" then
        return nil
    end
    local dataset = getSummons()
    local byId = dataset.byId or {}
    return byId[id]
end

-- Lookup by display Name (for editors)
local function findSummonByName(name)
    if not name or name == "" then
        return nil
    end
    local dataset = getSummons()
    for _, rec in ipairs(dataset.records or {}) do
        if rec["Name"] == name then
            return rec
        end
    end
    return nil
end

----------------------------------------------------------------------
-- Formatting helpers
----------------------------------------------------------------------

local function formatAttributes(list)
    if type(list) ~= "table" or #list == 0 then
        return nil
    end

    local parts = {}

    for _, attr in ipairs(list) do
        if type(attr) == "table" then
            local t = attr.Type or {}
            local name = t.Name or attr.ID or "Unknown"
            local val  = attr.Value

            local seg = name
            if type(val) == "table" then
                local base = val.Base
                local per  = val["Per Level"]
                local detail = {}

                if base then
                    table.insert(detail, string.format("Base %.1f", base))
                end
                if per then
                    table.insert(detail, string.format("%.1f / Lv", per))
                end

                if #detail > 0 then
                    seg = seg .. " – " .. table.concat(detail, ", ")
                end
            end

            table.insert(parts, seg)
        end
    end

    if #parts == 0 then
        return nil
    end

    return table.concat(parts, "<br />")
end

local function formatSkills(list)
    if type(list) ~= "table" or #list == 0 then
        return nil
    end

    local parts = {}

    for _, s in ipairs(list) do
        if type(s) == "table" then
            local name = s.Name or s.ID or "Unknown skill"
            local level = s.Level
            local cd    = s.Cooldown
            local cast  = s["Cast Time"]
            local th    = s["Target Health"]

            local seg = name
            local detail = {}

            if level then
                table.insert(detail, string.format("Lv.%s", tostring(level)))
            end
            if cd then
                table.insert(detail, string.format("CD %s s", tostring(cd)))
            end
            if cast then
                table.insert(detail, string.format("CT %s s", tostring(cast)))
            end
            if th then
                table.insert(detail, string.format("Target HP %.2f", th))
            end

            if #detail > 0 then
                seg = seg .. " (" .. table.concat(detail, ", ") .. ")"
            end

            table.insert(parts, seg)
        end
    end

    if #parts == 0 then
        return nil
    end

    return table.concat(parts, "<br />")
end

local function formatStages(list)
    if type(list) ~= "table" or #list == 0 then
        return nil
    end

    local parts = {}

    for _, st in ipairs(list) do
        if type(st) == "table" then
            local stage   = st.Stage
            local level   = st["Unlock Level"]
            local mName   = st["Monster Name"] or st["Monster ID"] or "Unknown"

            local label = ""
            if stage then
                label = tostring(stage)
            else
                label = "?"
            end

            local seg = "Stage " .. label .. ": " .. mName
            if level then
                seg = seg .. string.format(" (Unlock Lv.%s)", tostring(level))
            end

            table.insert(parts, seg)
        end
    end

    if #parts == 0 then
        return nil
    end

    return table.concat(parts, "<br />")
end

local function formatSummonSkill(rec)
    local name = rec["Skill Name"]
    local internal = rec["Skill Internal Name"]
    if name and internal and internal ~= "" then
        return string.format("%s (%s)", name, internal)
    end
    return name or internal
end

----------------------------------------------------------------------
-- Infobox builder
----------------------------------------------------------------------

local function buildInfobox(rec)
    local root = mw.html.create("table")
    root:addClass("wikitable spiritvale-summon-infobox")

    -- Header: name
    local title = rec.Name or rec["Internal Name"] or "Unknown Summon"

    local header = root:tag("tr")
    local headerCell = header:tag("th")
    headerCell:attr("colspan", 2)
    headerCell:wikitext(title)

    -- Basic info
    addRow(root, "Internal name", rec["Internal Name"])
    addRow(root, "Summon skill", formatSummonSkill(rec))

    -- Attributes
    local attrText = formatAttributes(rec.Attributes)
    addRow(root, "Attributes", attrText)

    -- Skills
    local skillsText = formatSkills(rec.Skills)
    addRow(root, "Skills", skillsText)

    -- Stages
    local stagesText = formatStages(rec.Stages)
    addRow(root, "Stages", stagesText)

    return tostring(root)
end

----------------------------------------------------------------------
-- Public entry point
----------------------------------------------------------------------

function p.infobox(frame)
    local args = getArgs(frame)

    -- Allow:
    --   {{Summon|Angel Mage}}         -> args[1] = "Angel Mage" (Name)
    --   {{Summon|name=Angel Mage}}    -> args.name
    --   {{Summon|id=Angel Mage}}      -> args.id (Internal Name)
    local raw1 = args[1]
    local name = args.name or raw1
    local id   = args.id

    local rec

    -- 1) Prefer display Name (what editors actually know)
    if name and name ~= "" then
        rec = findSummonByName(name)
    end

    -- 2) Fallback: Internal Name if explicitly given
    if not rec and id and id ~= "" then
        rec = getSummonById(id)
    end

    if not rec then
        local label = name or id or "?"
        return string.format(
            "<strong>Unknown summon:</strong> %s[[Category:Pages with unknown summon|%s]]",
            mw.text.nowiki(label),
            label
        )
    end

    return buildInfobox(rec)
end

return p