Zum Inhalt springen


Modul:HeroCard: Unterschied zwischen den Versionen

Aus Firestone Idle RPG Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
-- Modul:HeroCard
local p = {}
local p = {}


-- ======= CONFIG / DEFAULTS =======
-- ======= CONFIG / DEFAULTS =======
local CFG = {
local CFG = {
   -- Reihenfolge + Labels/Standard-Icons für Haupt-/Erweiterte Attribute
   -- Reihenfolge + Labels/Standard-Icons für Haupt-/Erweiterte Attribute (DE)
   main_order = { "damage", "hp", "armor" },
   main_order = { "damage", "hp", "armor" },
   main_meta = {
   main_meta = {
     damage = { label = "Damage",     icon = "AttackIcon.png"  },
     damage = { label = "Schaden",       icon = "AttackIcon.png"  },
     hp    = { label = "Health",     icon = "HealthIcon.png"  },
     hp    = { label = "Lebenspunkte", icon = "HealthIcon.png"  },
     armor  = { label = "Armor",      icon = "ArmorIcon.png" },
     armor  = { label = "Rüstung",      icon = "ArmorIcon.png"   },
   },
   },
   adv_order = { "precision", "bonus_damage", "speed", "dodge" },
   adv_order = { "precision", "bonus_damage", "speed", "dodge" },
   adv_meta = {
   adv_meta = {
     precision    = { label = "Precision",     icon = "CriticalChanceIcon.png"   },
     precision    = { label = "Präzision",     icon = "CriticalChanceIcon.png" },
     bonus_damage = { label = "Damage Bonus",   icon = "CriticalDamageIcon.png" },
     bonus_damage = { label = "Schadensbonus", icon = "CriticalDamageIcon.png" },
     speed        = { label = "Speed",         icon = "AttackSpeedIcon.png"    },
     speed        = { label = "Tempo",         icon = "AttackSpeedIcon.png"    },
     dodge        = { label = "Dodge",         icon = "DodgeIcon.png"   },
     dodge        = { label = "Ausweichen",   icon = "DodgeIcon.png"         },
   },
   },


Zeile 22: Zeile 21:
   icon_map = {
   icon_map = {
     attackstyle = {
     attackstyle = {
       malee       = "MeleeIcon.png",
       melee       = "MeleeIcon.png",
       spellcaster = "SpellcasterIcon.png",
       spellcaster = "SpellcasterIcon.png",
       ranged      = "RangedIcon.png",
       ranged      = "RangedIcon.png",
Zeile 46: Zeile 45:
-- ======= HELPERS =======
-- ======= HELPERS =======
local function isempty(v) return v == nil or v == "" end
local function isempty(v) return v == nil or v == "" end
-- nil-sicheres pick: nimmt den ersten nicht-leeren Wert
local function pick(...)
local function pick(...)
   local n = select('#', ...)            -- echte Anzahl der Argumente (inkl. nil)
   local n = select('#', ...)
   for i = 1, n do
   for i = 1, n do
     local v = select(i, ...)
     local v = select(i, ...)
     if not (v == nil or v == "") then
     if not (v == nil or v == "") then return v end
      return v
    end
   end
   end
   return nil
   return nil
end
end


local function norm(s)
local function norm(s)
   if isempty(s) then return "" end
   if isempty(s) then return "" end
   s = mw.ustring.lower(mw.text.trim(s))
   s = mw.ustring.lower(mw.text.trim(s)):gsub("%s+", " ")
  s = s:gsub("%s+", " ")
   return s
   return s
end
end


local function getArgs(frame)
local function getArgs(frame)
   local args = {}
   local args, f1, f2 = {}, frame.args or {}, (frame:getParent() and frame:getParent().args) or {}
  local f1, f2 = frame.args or {}, (frame:getParent() and frame:getParent().args) or {}
   for k,v in pairs(f1) do if not isempty(v) then args[k]=v end end
   for k,v in pairs(f1) do if not isempty(v) then args[k]=v end end
   for k,v in pairs(f2) do if not isempty(v) then args[k]=v end end
   for k,v in pairs(f2) do if not isempty(v) then args[k]=v end end
Zeile 73: Zeile 68:
end
end


-- Versucht beide Namensräume (de/en) zu laden und gibt die Heroes-Tabelle zurück.
-- Heldendaten laden (DE/EN Namespace)
local function loadHeroes()
local function loadHeroes()
   local tried, lastErr = {}, nil
   local tried, lastErr = {}, nil
Zeile 81: Zeile 76:
       return mod.heroes, { ok=true, from=title }
       return mod.heroes, { ok=true, from=title }
     else
     else
       table.insert(tried, title)
       table.insert(tried, title); lastErr = mod
      lastErr = mod
     end
     end
   end
   end
Zeile 94: Zeile 88:
   if opts and opts.link  then table.insert(parts, "link=" .. opts.link) end
   if opts and opts.link  then table.insert(parts, "link=" .. opts.link) end
   if opts and opts.class then table.insert(parts, "class=" .. opts.class) end
   if opts and opts.class then table.insert(parts, "class=" .. opts.class) end
  if opts and opts.lazy  then table.insert(parts, "loading=lazy") end
   return string.format("[[%s]]", table.concat(parts, "|"))
   return string.format("[[%s]]", table.concat(parts, "|"))
end
end
Zeile 103: Zeile 98:
end
end


-- Pill/Adv mit Icon-Größe 36px
local function pill(htmlParent, label, val, icon)
local function pill(htmlParent, label, val, icon)
   if isempty(val) then return end
   if isempty(val) then return end
   local box  = htmlParent:tag("div"):addClass("kr-hc-pill")
   local box  = htmlParent:tag("div"):addClass("kr-hc-pill")
   local left = box:tag("div"):addClass("kr-hc-pill-icon")
   local left = box:tag("div"):addClass("kr-hc-pill-icon")
   if not isempty(icon) then left:wikitext(fileWikitext(icon, { size="24x24px", link="" })) end
   if not isempty(icon) then left:wikitext(fileWikitext(icon, { size="36x36px", link="", lazy=true })) end
   local text = box:tag("div"):addClass("kr-hc-pill-text")
   local text = box:tag("div"):addClass("kr-hc-pill-text")
   text:tag("div"):addClass("kr-hc-pill-label"):wikitext(label or "")
   text:tag("div"):addClass("kr-hc-pill-label"):wikitext(label or "")
Zeile 117: Zeile 113:
   local box  = htmlParent:tag("div"):addClass("kr-hc-adv")
   local box  = htmlParent:tag("div"):addClass("kr-hc-adv")
   local left = box:tag("div"):addClass("kr-hc-adv-icon")
   local left = box:tag("div"):addClass("kr-hc-adv-icon")
   if not isempty(icon) then left:wikitext(fileWikitext(icon, { size="24x24px", link="" })) end
   if not isempty(icon) then left:wikitext(fileWikitext(icon, { size="36x36px", link="", lazy=true })) end
   local main = box:tag("div"):addClass("kr-hc-adv-main")
   local main = box:tag("div"):addClass("kr-hc-adv-main")
   main:tag("div"):addClass("kr-hc-adv-label"):wikitext(label or "")
   main:tag("div"):addClass("kr-hc-adv-label"):wikitext(label or "")
   main:tag("div"):addClass("kr-hc-adv-value"):wikitext(val)
   main:tag("div"):addClass("kr-hc-adv-value"):wikitext(val)
end
local function fmtUnlock(unlock_at)
  if isempty(unlock_at) then return nil end
  local s = mw.text.trim(unlock_at)
  s = s:gsub("^Stage%s*(%d+)%s*$", "Stufe %1") -- EN → DE
  return s
end
end


Zeile 129: Zeile 132:


   local heroes, info = loadHeroes()
   local heroes, info = loadHeroes()
   local data = (heroes and heroes[name]) or (heroes and heroes[mw.ustring.gsub(name or "", "_", " ")]) or {}
   local data = (heroes and (heroes[name] or heroes[mw.ustring.gsub(name or "", "_", " ")])) or {}


   local debugWanted = norm(args.debug) == "1" or norm(args.debug) == "yes"
   local debugWanted = norm(args.debug) == "1" or norm(args.debug) == "yes"
Zeile 153: Zeile 156:
   local root = mw.html.create("div"):addClass("kr-hero-card")
   local root = mw.html.create("div"):addClass("kr-hero-card")


   -- Avatar (links)
   -- Avatar (links) – skaliert, lazy
   local fig = root:tag("div"):addClass("kr-hc-avatar")
   root:tag("div"):addClass("kr-hc-avatar")
  fig:wikitext(fileWikitext(avatar, { class="kr-hc-avatar-img", link=linkto }))
      :wikitext(fileWikitext(avatar, { class="kr-hc-avatar-img", link=linkto, lazy=true }))


   -- Content (rechts)
   -- Content (rechts)
   local content = root:tag("div"):addClass("kr-hc-content")
   local content = root:tag("div"):addClass("kr-hc-content")
  if not isempty(name) then content:tag("h2"):addClass("kr-hc-name"):wikitext(name) end


   -- Wenn das Datenmodul nicht geladen werden konnte, optional Debug-Hinweis
   -- Titel: Name verlinkt + (Unlock)
  if not isempty(name) then
    local h = content:tag("h2"):addClass("kr-hc-name")
    local unlock_text = fmtUnlock(unlock_at)
    h:wikitext(string.format("[[%s|%s]]%s",
      linkto, name,
      unlock_text and (" <span class=\"kr-hc-unlock\">(" .. unlock_text .. ")</span>") or ""
    ))
  end
 
  -- Debug: Datenmodul?
   if (not info or not info.ok) and debugWanted then
   if (not info or not info.ok) and debugWanted then
     content:tag("div"):addClass("kr-hc-debug")
     content:tag("div"):addClass("kr-hc-debug")
Zeile 168: Zeile 180:
   end
   end


   -- Basics
   -- ===== Basics als 2×2-Grid (Icon RECHTS, 24px) =====
   local basics = content:tag("div"):addClass("kr-hc-basics")
   local basicsGrid = content:tag("div"):addClass("kr-hc-basics2")
   local function row(label, value, icon)
   local function pair(label, value, icon)
     if isempty(value) then return end
     if isempty(value) then return end
     basics:tag("div"):addClass("kr-hc-kv kr-hc-k"):wikitext(label)
     local it = basicsGrid:tag("div"):addClass("kr-hc-basic")
     local cell = basics:tag("div"):addClass("kr-hc-kv kr-hc-v")
    it:tag("span"):addClass("label"):wikitext(label)
     if not isempty(icon) then cell:tag("span"):addClass("kr-hc-v-icon"):wikitext(fileWikitext(icon, { size="20x20px", link="" })) end
     local v = it:tag("span"):addClass("value")
     cell:wikitext(value)
    v:wikitext(value)
     if not isempty(icon) then
      v:tag("span"):addClass("value-icon")
        :wikitext(fileWikitext(icon, { size="24x24px", link="", lazy=true }))
     end
   end
   end
   row("Class:",         class,          nil)
   pair("Klasse:",       class,          nil)                 -- kein Icon für Klasse
   row("Attack style:",   attackstyle,   attackstyle_icon)
   pair("Spezialisierung:", specialization, specialization_icon)
   row("Specialization:", specialization, specialization_icon)
   pair("Angriffsart:",   attackstyle,   attackstyle_icon)
   row("Resource:",       resource,      resource_icon)
   pair("Ressource:",     resource,      resource_icon)
  row("Unlock at:",      unlock_at,      nil)


   -- Main attributes
   -- ===== Hauptattribute =====
   content:tag("h3"):addClass("kr-hc-h"):wikitext("Main attributes")
   content:tag("h3"):addClass("kr-hc-h"):wikitext("Hauptattribute")
   local mains  = content:tag("div"):addClass("kr-hc-main-attrs")
   local mains  = content:tag("div"):addClass("kr-hc-main-attrs")
   local heroMain= type(data.main) == "table" and data.main or {}
   local heroMain= type(data.main) == "table" and data.main or {}
Zeile 195: Zeile 210:
   end
   end


   -- Advanced attributes
   -- ===== Erweiterte Attribute =====
   content:tag("h3"):addClass("kr-hc-h"):wikitext("Advanced attributes")
   content:tag("h3"):addClass("kr-hc-h"):wikitext("Erweiterte Attribute")
   local advs  = content:tag("div"):addClass("kr-hc-adv-attrs")
   local advs  = content:tag("div"):addClass("kr-hc-adv-attrs")
   local heroAdv= type(data.adv) == "table" and data.adv or {}
   local heroAdv= type(data.adv) == "table" and data.adv or {}
Zeile 212: Zeile 227:
   end
   end


   -- Awakening bonus
   -- ===== Erweckungsbonus (Icon 64px) =====
   if not isempty(awakening_bonus) then
   if not isempty(awakening_bonus) then
     content:tag("h3"):addClass("kr-hc-h"):wikitext("Awakening bonus")
     content:tag("h3"):addClass("kr-hc-h"):wikitext("Erweckungsbonus")
     local awk = content:tag("div"):addClass("kr-hc-awakening")
     local awk = content:tag("div"):addClass("kr-hc-awakening")
     local box = awk:tag("div"):addClass("kr-hc-adv")
     local box = awk:tag("div"):addClass("kr-hc-adv")
     if not isempty(awakening_bonus_icon) then
     if not isempty(awakening_bonus_icon) then
       box:tag("div"):addClass("kr-hc-adv-icon")
       box:tag("div"):addClass("kr-hc-adv-icon")
         :wikitext(fileWikitext(awakening_bonus_icon, { size="24x24px", link="" }))
         :wikitext(fileWikitext(awakening_bonus_icon, { size="64x64px", link="", lazy=true }))
     end
     end
     local main = box:tag("div"):addClass("kr-hc-adv-main")
     local main = box:tag("div"):addClass("kr-hc-adv-main")
Zeile 230: Zeile 245:
     content:tag("pre"):addClass("kr-hc-debug")
     content:tag("pre"):addClass("kr-hc-debug")
       :wikitext(mw.text.nowiki(mw.dumpObject({
       :wikitext(mw.text.nowiki(mw.dumpObject({
         name = name,
         name = name, data = data, module = info
        dataFound = (heroes and heroes[name]) ~= nil,
        heroesModule = info,
        data = data
       })))
       })))
   end
   end
Zeile 240: Zeile 252:
end
end


-- Selbsttest: zeigt, ob HeroData gefunden wird und listet Heldennamen
-- Selbsttest
function p.selftest()
function p.selftest()
   local heroes, info = loadHeroes()
   local heroes, info = loadHeroes()
Zeile 249: Zeile 261:
   end
   end
   out:tag("p"):wikitext("HeroData loaded from: " .. info.from)
   out:tag("p"):wikitext("HeroData loaded from: " .. info.from)
   local ul = out:tag("ul")
   local ul = out:tag("ul"); for name,_ in pairs(heroes) do ul:tag("li"):wikitext(name) end
  for name,_ in pairs(heroes) do ul:tag("li"):wikitext(name) end
   return tostring(out)
   return tostring(out)
end
end


return p
return p

Version vom 8. Oktober 2025, 18:49 Uhr

Die Dokumentation für dieses Modul kann unter Modul:HeroCard/Doku erstellt werden

local p = {}

-- ======= CONFIG / DEFAULTS =======
local CFG = {
  -- Reihenfolge + Labels/Standard-Icons für Haupt-/Erweiterte Attribute (DE)
  main_order = { "damage", "hp", "armor" },
  main_meta = {
    damage = { label = "Schaden",       icon = "AttackIcon.png"  },
    hp     = { label = "Lebenspunkte",  icon = "HealthIcon.png"  },
    armor  = { label = "Rüstung",       icon = "ArmorIcon.png"   },
  },
  adv_order = { "precision", "bonus_damage", "speed", "dodge" },
  adv_meta = {
    precision    = { label = "Präzision",     icon = "CriticalChanceIcon.png" },
    bonus_damage = { label = "Schadensbonus", icon = "CriticalDamageIcon.png" },
    speed        = { label = "Tempo",         icon = "AttackSpeedIcon.png"    },
    dodge        = { label = "Ausweichen",    icon = "DodgeIcon.png"          },
  },

  -- Icon-Mapping nach Wert (case-/whitespace-insensitiv)
  icon_map = {
    attackstyle = {
      melee       = "MeleeIcon.png",
      spellcaster = "SpellcasterIcon.png",
      ranged      = "RangedIcon.png",
    },
    specialization = {
      healer = "HealerIcon.png",
      damage = "DamageIcon.png",
      tank   = "TankIcon.png",
    },
    resource = {
      mana   = "ManaIcon.png",
      rage   = "RageIcon.png",
      energy = "EnergyIcon.png",
    },
    awakening_bonus = {
      ["raining gold"]        = "RainingGold.png",
      ["all main attributes"] = "AllAttributes.png",
      prestigious             = "Prestigious.png",
    }
  }
}

-- ======= HELPERS =======
local function isempty(v) return v == nil or v == "" end
-- nil-sicheres pick: nimmt den ersten nicht-leeren Wert
local function pick(...)
  local n = select('#', ...)
  for i = 1, n do
    local v = select(i, ...)
    if not (v == nil or v == "") then return v end
  end
  return nil
end

local function norm(s)
  if isempty(s) then return "" end
  s = mw.ustring.lower(mw.text.trim(s)):gsub("%s+", " ")
  return s
end

local function getArgs(frame)
  local args, f1, f2 = {}, frame.args or {}, (frame:getParent() and frame:getParent().args) or {}
  for k,v in pairs(f1) do if not isempty(v) then args[k]=v end end
  for k,v in pairs(f2) do if not isempty(v) then args[k]=v end end
  return args
end

-- Heldendaten laden (DE/EN Namespace)
local function loadHeroes()
  local tried, lastErr = {}, nil
  for _,title in ipairs{ "Modul:HeroData", "Module:HeroData" } do
    local ok, mod = pcall(require, title)
    if ok and type(mod) == "table" and type(mod.heroes) == "table" then
      return mod.heroes, { ok=true, from=title }
    else
      table.insert(tried, title); lastErr = mod
    end
  end
  return nil, { ok=false, tried=tried, err=lastErr }
end

local function fileWikitext(filename, opts)
  if isempty(filename) then return "" end
  local parts = { ("Datei:%s"):format(filename) }
  if opts and opts.size  then table.insert(parts, opts.size) end
  if opts and opts.link  then table.insert(parts, "link=" .. opts.link) end
  if opts and opts.class then table.insert(parts, "class=" .. opts.class) end
  if opts and opts.lazy  then table.insert(parts, "loading=lazy") end
  return string.format("[[%s]]", table.concat(parts, "|"))
end

local function mapIcon(kind, value, override)
  if not isempty(override) then return override end
  local t = CFG.icon_map[kind] or {}
  return t[norm(value)]
end

-- Pill/Adv mit Icon-Größe 36px
local function pill(htmlParent, label, val, icon)
  if isempty(val) then return end
  local box  = htmlParent:tag("div"):addClass("kr-hc-pill")
  local left = box:tag("div"):addClass("kr-hc-pill-icon")
  if not isempty(icon) then left:wikitext(fileWikitext(icon, { size="36x36px", link="", lazy=true })) end
  local text = box:tag("div"):addClass("kr-hc-pill-text")
  text:tag("div"):addClass("kr-hc-pill-label"):wikitext(label or "")
  text:tag("div"):addClass("kr-hc-pill-value"):wikitext(val)
end

local function adv(htmlParent, label, val, icon)
  if isempty(val) then return end
  local box  = htmlParent:tag("div"):addClass("kr-hc-adv")
  local left = box:tag("div"):addClass("kr-hc-adv-icon")
  if not isempty(icon) then left:wikitext(fileWikitext(icon, { size="36x36px", link="", lazy=true })) end
  local main = box:tag("div"):addClass("kr-hc-adv-main")
  main:tag("div"):addClass("kr-hc-adv-label"):wikitext(label or "")
  main:tag("div"):addClass("kr-hc-adv-value"):wikitext(val)
end

local function fmtUnlock(unlock_at)
  if isempty(unlock_at) then return nil end
  local s = mw.text.trim(unlock_at)
  s = s:gsub("^Stage%s*(%d+)%s*$", "Stufe %1") -- EN → DE
  return s
end

-- ======= RENDER =======
function p.render(frame)
  local args = getArgs(frame)
  local name = pick(args.name, args[1]) or ""

  local heroes, info = loadHeroes()
  local data = (heroes and (heroes[name] or heroes[mw.ustring.gsub(name or "", "_", " ")])) or {}

  local debugWanted = norm(args.debug) == "1" or norm(args.debug) == "yes"

  -- Basics (Werte)
  local class          = pick(args.class,          data.class)
  local attackstyle    = pick(args.attackstyle,    data.attackstyle)
  local specialization = pick(args.specialization, data.specialization)
  local resource       = pick(args.resource,       data.resource)
  local unlock_at      = pick(args.unlock_at,      data.unlock_at)
  local awakening_bonus= pick(args.awakening_bonus,data.awakening_bonus)

  -- Icons (kein Icon für class)
  local attackstyle_icon    = mapIcon("attackstyle",    attackstyle,    pick(args.attackstyle_icon,    data.attackstyle_icon))
  local specialization_icon = mapIcon("specialization", specialization, pick(args.specialization_icon, data.specialization_icon))
  local resource_icon       = mapIcon("resource",       resource,       pick(args.resource_icon,       data.resource_icon))
  local awakening_bonus_icon= mapIcon("awakening_bonus",awakening_bonus,pick(args.awakening_bonus_icon,data.awakening_bonus_icon))

  -- Bilder/Links
  local linkto = pick(args.linkto, name)
  local avatar = pick(args.avatar, (not isempty(name) and (name .. ".png")) or nil)

  local root = mw.html.create("div"):addClass("kr-hero-card")

  -- Avatar (links) – skaliert, lazy
  root:tag("div"):addClass("kr-hc-avatar")
      :wikitext(fileWikitext(avatar, { class="kr-hc-avatar-img", link=linkto, lazy=true }))

  -- Content (rechts)
  local content = root:tag("div"):addClass("kr-hc-content")

  -- Titel: Name verlinkt + (Unlock)
  if not isempty(name) then
    local h = content:tag("h2"):addClass("kr-hc-name")
    local unlock_text = fmtUnlock(unlock_at)
    h:wikitext(string.format("[[%s|%s]]%s",
      linkto, name,
      unlock_text and (" <span class=\"kr-hc-unlock\">(" .. unlock_text .. ")</span>") or ""
    ))
  end

  -- Debug: Datenmodul?
  if (not info or not info.ok) and debugWanted then
    content:tag("div"):addClass("kr-hc-debug")
      :wikitext("<strong>HeroData not loaded.</strong> Tried: "
        .. mw.text.nowiki(table.concat((info and info.tried) or {}, ", ")))
  end

  -- ===== Basics als 2×2-Grid (Icon RECHTS, 24px) =====
  local basicsGrid = content:tag("div"):addClass("kr-hc-basics2")
  local function pair(label, value, icon)
    if isempty(value) then return end
    local it = basicsGrid:tag("div"):addClass("kr-hc-basic")
    it:tag("span"):addClass("label"):wikitext(label)
    local v = it:tag("span"):addClass("value")
    v:wikitext(value)
    if not isempty(icon) then
      v:tag("span"):addClass("value-icon")
        :wikitext(fileWikitext(icon, { size="24x24px", link="", lazy=true }))
    end
  end
  pair("Klasse:",        class,          nil)                  -- kein Icon für Klasse
  pair("Spezialisierung:", specialization, specialization_icon)
  pair("Angriffsart:",   attackstyle,    attackstyle_icon)
  pair("Ressource:",     resource,       resource_icon)

  -- ===== Hauptattribute =====
  content:tag("h3"):addClass("kr-hc-h"):wikitext("Hauptattribute")
  local mains   = content:tag("div"):addClass("kr-hc-main-attrs")
  local heroMain= type(data.main) == "table" and data.main or {}
  for i, key in ipairs(CFG.main_order) do
    local meta  = CFG.main_meta[key]
    local val   = pick(args["main"..i.."_val"], heroMain[key])
    local label = pick(args["main"..i.."_label"], meta.label)
    local icon  = pick(args["main"..i.."_icon"],  meta.icon)
    pill(mains, label, val, icon)
  end

  -- ===== Erweiterte Attribute =====
  content:tag("h3"):addClass("kr-hc-h"):wikitext("Erweiterte Attribute")
  local advs   = content:tag("div"):addClass("kr-hc-adv-attrs")
  local heroAdv= type(data.adv) == "table" and data.adv or {}
  for i, key in ipairs(CFG.adv_order) do
    local meta  = CFG.adv_meta[key]
    local val   = pick(args["adv"..i.."_val"], heroAdv[key])
    local label = pick(args["adv"..i.."_label"], meta.label)
    local icon  = pick(args["adv"..i.."_icon"],  meta.icon)
    adv(advs, label, val, icon)
  end
  for i = #CFG.adv_order + 1, 8 do
    local val = args["adv"..i.."_val"]
    if isempty(val) then break end
    adv(advs, args["adv"..i.."_label"] or "", val, args["adv"..i.."_icon"])
  end

  -- ===== Erweckungsbonus (Icon 64px) =====
  if not isempty(awakening_bonus) then
    content:tag("h3"):addClass("kr-hc-h"):wikitext("Erweckungsbonus")
    local awk = content:tag("div"):addClass("kr-hc-awakening")
    local box = awk:tag("div"):addClass("kr-hc-adv")
    if not isempty(awakening_bonus_icon) then
      box:tag("div"):addClass("kr-hc-adv-icon")
         :wikitext(fileWikitext(awakening_bonus_icon, { size="64x64px", link="", lazy=true }))
    end
    local main = box:tag("div"):addClass("kr-hc-adv-main")
    main:tag("div"):addClass("kr-hc-adv-label"):wikitext("Bonus")
    main:tag("div"):addClass("kr-hc-adv-value"):wikitext(awakening_bonus)
  end

  -- Extra-Debug
  if debugWanted then
    content:tag("pre"):addClass("kr-hc-debug")
      :wikitext(mw.text.nowiki(mw.dumpObject({
        name = name, data = data, module = info
      })))
  end

  return tostring(root)
end

-- Selbsttest
function p.selftest()
  local heroes, info = loadHeroes()
  local out = mw.html.create("div")
  if not (info and info.ok) then
    out:tag("p"):wikitext("HeroData NOT loaded. Tried: " .. mw.text.nowiki(table.concat((info and info.tried) or {}, ", ")))
    return tostring(out)
  end
  out:tag("p"):wikitext("HeroData loaded from: " .. info.from)
  local ul = out:tag("ul"); for name,_ in pairs(heroes) do ul:tag("li"):wikitext(name) end
  return tostring(out)
end

return p