Modul:HeroCard: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
| (9 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 14: | Zeile 14: | ||
end | end | ||
-- Lazy load | -- Lazy load | ||
local CFG, I18N | local CFG, I18N | ||
local function loadConfig() | local function loadConfig() | ||
| Zeile 28: | Zeile 28: | ||
local ok, mod = pcall(require, t); if ok and type(mod)=="table" then I18N=mod; return I18N end | local ok, mod = pcall(require, t); if ok and type(mod)=="table" then I18N=mod; return I18N end | ||
end | end | ||
I18N = { i18n = {}, aw_desc = {}, ui = {} }; return I18N | I18N = { i18n={}, aw_desc={}, ui={} }; return I18N | ||
end | end | ||
-- HeroData | -- HeroData | ||
local function loadHeroes() | local function loadHeroes() | ||
for _,t in ipairs{ "Modul:HeroData", "Module:HeroData" } do | for _,t in ipairs{ "Modul:HeroData", "Module:HeroData" } do | ||
local ok, mod = pcall(require, t) | local ok, mod = pcall(require, t) | ||
if ok and type(mod)=="table" and type(mod.heroes)=="table" then | if ok and type(mod)=="table" and type(mod.heroes)=="table" then | ||
return mod.heroes | return mod.heroes | ||
end | end | ||
end | end | ||
return nil | return nil | ||
end | end | ||
| Zeile 51: | Zeile 51: | ||
end | end | ||
-- Übersetzen | -- Übersetzen (HeroI18n.i18n[kind][key_en]) | ||
local function tr(kind, value) | local function tr(kind, value) | ||
if isempty(value) then return value end | if isempty(value) then return value end | ||
| Zeile 58: | Zeile 58: | ||
end | end | ||
-- Tooltip- | -- Tooltip-Text (aw_desc[key_en]) | ||
local function | local function aw_desc(key_en) | ||
if | if isempty(key_en) then return nil end | ||
return (loadI18n().aw_desc or {})[norm(key_en)] | |||
end | end | ||
-- Unlock-Text über I18n | |||
local function unlock_text(ui, raw) | |||
if isempty(raw) then return nil end | |||
local t = norm(raw) | |||
local n = t:match("stage%s*(%d+)") | |||
if n and ui.unlock and ui.unlock.at_stage then | |||
return (ui.unlock.at_stage:gsub("%$1", n)) | |||
end | |||
if t:match("pirate%s*ship") and ui.unlock and ui.unlock.pirate_ship then | |||
return ui.unlock.pirate_ship | |||
end | |||
return raw | |||
-- Unlock-Text | |||
local function | |||
if isempty( | |||
local t = norm( | |||
local n = t:match("stage%s*(%d+)") | |||
if t:match("pirate%s*ship") | |||
return | |||
end | end | ||
-- Default-Avatar | |||
local function defaultAvatarFile(name, data, args) | local function defaultAvatarFile(name, data, args) | ||
if not isempty(args.avatar) then return args.avatar end | if not isempty(args.avatar) then return args.avatar end | ||
| Zeile 129: | Zeile 98: | ||
end | end | ||
local frame = mw.getCurrentFrame() | local frame = mw.getCurrentFrame() | ||
local defaultLabel = (loadI18n().ui and loadI18n().ui.default_skin) or " | local defaultLabel = (loadI18n().ui and loadI18n().ui.default_skin) or "" | ||
local panels = {} | local panels = {} | ||
| Zeile 146: | Zeile 115: | ||
if skinsTable[1] ~= nil then | if skinsTable[1] ~= nil then | ||
local idx = 1 | local idx = 1 | ||
for _, v in ipairs(skinsTable) do | for _, v in ipairs(skinsTable) do | ||
local label, file | |||
if type(v) == "table" then | if type(v) == "table" then | ||
label = v.label or v[1] | |||
file = v.file or (name .. "_" .. (v.filename or label) .. ".png") | |||
else | else | ||
label = tostring(v) | |||
file = name .. "_" .. label .. ".png" | |||
end | end | ||
idx = idx + 1 | |||
push(isGuardian and ("E"..idx) or label, file) | |||
end | end | ||
else | else | ||
local tmp = {} | local tmp, idx = {}, 1 | ||
for k, v in pairs(skinsTable) do | for k, v in pairs(skinsTable) do | ||
local label, file | |||
if type(v) == "table" then | if type(v) == "table" then | ||
label = v.label or v[1] or tostring(k) | |||
file = v.file or (name .. "_" .. (v.filename or label) .. ".png") | |||
else | else | ||
label = tostring(v) | |||
file = name .. "_" .. label .. ".png" | |||
end | end | ||
table.insert(tmp, {label=label, file=file}) | |||
end | end | ||
table.sort(tmp, function(a,b) return tostring(a.label) < tostring(b.label) end) | table.sort(tmp, function(a,b) return tostring(a.label) < tostring(b.label) end) | ||
for _,it in ipairs(tmp) do | for _,it in ipairs(tmp) do | ||
idx = idx + 1 | idx = idx + 1 | ||
push(isGuardian and ("E"..idx) or it.label, it.file) | |||
end | end | ||
end | end | ||
| Zeile 187: | Zeile 152: | ||
end | end | ||
-- | -- Profile-Auswahl | ||
local function pickProfile(cfg, entity) | local function pickProfile(cfg, entity) | ||
local profiles = cfg.profiles or {} | local profiles = cfg.profiles or {} | ||
local base = profiles.default or {} | local base = profiles.default or {} | ||
local over = profiles[entity] or {} | |||
local over = profiles[ | |||
local P = {} | local P = {} | ||
| Zeile 205: | Zeile 163: | ||
P.adv_order = over.adv_order or base.adv_order or {} | P.adv_order = over.adv_order or base.adv_order or {} | ||
P.adv_meta = over.adv_meta or base.adv_meta or {} | P.adv_meta = over.adv_meta or base.adv_meta or {} | ||
-- Guardian: Chaos | |||
-- Guardian: Chaos | |||
P.chaos_main_order = over.chaos_main_order or {} | P.chaos_main_order = over.chaos_main_order or {} | ||
P.chaos_main_meta = over.chaos_main_meta or {} | P.chaos_main_meta = over.chaos_main_meta or {} | ||
P.chaos_adv_order = over.chaos_adv_order or {} | P.chaos_adv_order = over.chaos_adv_order or {} | ||
P.chaos_adv_meta = over.chaos_adv_meta or {} | P.chaos_adv_meta = over.chaos_adv_meta or {} | ||
return P | |||
end | |||
return | -- Tooltip-HTML | ||
local function addTooltip(box, title, desc) | |||
if (isempty(title) and isempty(desc)) then return end | |||
local tip = box:tag("span"):addClass("kr-tipbox") | |||
if not isempty(title) then tip:tag("span"):addClass("kr-tip-title"):wikitext(title) end | |||
if not (isempty(title) or isempty(desc)) then tip:tag("span"):addClass("kr-tip-sep") end | |||
if not isempty(desc) then tip:tag("span"):addClass("kr-tip-desc"):wikitext(desc) end | |||
end | |||
-- Kleine Bausteine: Hauptattribute-Kachel | |||
local function pill(parent, label, val, icon, showLabels, desc) | |||
if isempty(val) then return end | |||
local box = parent:tag("div"):addClass("kr-hc-pill kr-tip") | |||
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") | |||
local lab = text:tag("div"):addClass("kr-hc-pill-label"):wikitext(label or "") | |||
if not showLabels then lab:addClass("is-hidden") end | |||
text:tag("div"):addClass("kr-hc-pill-value"):wikitext(val) | |||
addTooltip(box, label, desc) | |||
end | |||
-- Kleine Bausteine: Fortschritts-Kachel | |||
local function adv(parent, label, val, icon, showLabels, desc) | |||
if isempty(val) then return end | |||
local box = parent:tag("div"):addClass("kr-hc-adv kr-tip") | |||
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") | |||
local lab = main:tag("div"):addClass("kr-hc-adv-label"):wikitext(label or "") | |||
if not showLabels then lab:addClass("is-hidden") end | |||
main:tag("div"):addClass("kr-hc-adv-value"):wikitext(val) | |||
addTooltip(box, label, desc) | |||
end | end | ||
-- | -- 3er-Grid Renderer | ||
local function render_grid(opts) | local function render_grid(opts) | ||
local profile, data, | local profile, data, L, UI, showLabels, entity = opts.profile, opts.data, opts.L, opts.UI, opts.showLabels, opts.entity | ||
local mode = opts.mode or "default" -- "default" | "chaos" | local mode = opts.mode or "default" -- "default" | "chaos" | ||
local abilityLabel = (UI.labels and UI.labels.wm and UI.labels.wm.ability) or "" | |||
local H_main = (UI.headers and UI.headers.main) or "" | |||
local H_adv = (UI.headers and UI.headers.adv) or "" | |||
local grid = mw.html.create("div"):addClass("kr-g3") | local grid = mw.html.create("div"):addClass("kr-g3") | ||
-- Spalte 1: Hauptattribute | |||
do | do | ||
local col = grid:tag("div"):addClass("kr-quad") | local col = grid:tag("div"):addClass("kr-quad") | ||
col:tag("h3"):addClass("kr-hc-h"):wikitext( | col:tag("h3"):addClass("kr-hc-h"):wikitext(H_main) | ||
local mains = col:tag("div"):addClass("kr-hc-main-attrs") | local mains = col:tag("div"):addClass("kr-hc-main-attrs") | ||
local src = (mode=="chaos") | local src = (mode=="chaos") and ((type(data.chaos)=="table" and (data.chaos.main or {})) or {}) or (data.main or {}) | ||
local order = (mode=="chaos") and profile.chaos_main_order or profile.main_order | local order = (mode=="chaos") and profile.chaos_main_order or profile.main_order | ||
local metaT | local metaT = (mode=="chaos") and profile.chaos_main_meta or profile.main_meta | ||
for _, key in ipairs(order) do | |||
for | |||
local meta = metaT[key] or { label = key } | local meta = metaT[key] or { label = key } | ||
local val = | local val = src[key] | ||
if not isempty(val) then | |||
pill(mains, meta.label, val, meta.icon, showLabels, meta.desc) | |||
end | |||
end | end | ||
end | end | ||
-- Spalte 2: Fortschrittsattribute | |||
do | do | ||
local col = grid:tag("div"):addClass("kr-quad") | local col = grid:tag("div"):addClass("kr-quad") | ||
col:tag("h3"):addClass("kr-hc-h"):wikitext( | col:tag("h3"):addClass("kr-hc-h"):wikitext(H_adv) | ||
local advs = col:tag("div"):addClass("kr-hc-adv-attrs") | local advs = col:tag("div"):addClass("kr-hc-adv-attrs") | ||
local src = (mode=="chaos") | local src = (mode=="chaos") and ((type(data.chaos)=="table" and (data.chaos.adv or {})) or {}) or (data.adv or {}) | ||
local order = (mode=="chaos") and profile.chaos_adv_order or profile.adv_order | local order = (mode=="chaos") and profile.chaos_adv_order or profile.adv_order | ||
local metaT | local metaT = (mode=="chaos") and profile.chaos_adv_meta or profile.adv_meta | ||
for _, key in ipairs(order) do | |||
for | |||
local meta = metaT[key] or { label = key } | local meta = metaT[key] or { label = key } | ||
local val = | local val = src[key] | ||
if not isempty(val) then | |||
adv(advs, meta.label, val, meta.icon, showLabels, meta.desc) | |||
end | |||
end | end | ||
end | end | ||
-- Spalte 3: Awakening / Aura / Milestone + (WM) Fähigkeit | |||
do | do | ||
local col = grid:tag("div"):addClass("kr-quad") | local col = grid:tag("div"):addClass("kr-quad") | ||
col:tag("h3"):addClass("kr-hc-h"):wikitext(L.awakening) | col:tag("h3"):addClass("kr-hc-h"):wikitext(L.awakening) | ||
local | local id_en, display_val | ||
and | if mode=="chaos" and entity=="guardian" then | ||
id_en = type(data.chaos)=="table" and data.chaos.aura_id or nil | |||
display_val= type(data.chaos)=="table" and data.chaos.aura_value or nil | |||
else | |||
if entity=="guardian" then | |||
id_en = data.aura_id | |||
display_val = data.aura_value | |||
elseif entity=="wm" then | |||
id_en = data.milestone_id | |||
else | |||
id_en = data.awakening_id | |||
end | |||
end | |||
local icon_map = ((opts.icon_map or {}).awakening_bonus) or {} | |||
local icon_file= icon_map[norm(id_en)] | |||
local title = tr("awakening_bonus", id_en) | |||
local desc = aw_desc(id_en) | |||
if not isempty( | if not (isempty(title) and isempty(display_val)) then | ||
local box = col:tag("div"):addClass("kr-hc-awakening kr-hc-adv") | local box = col:tag("div"):addClass("kr-hc-awakening kr-hc-adv kr-tip") | ||
if | if not (isempty(title) and isempty(desc)) then | ||
box:addClass("kr- | local tip = box:tag("span"):addClass("kr-tipbox") | ||
if not isempty(title) then tip:tag("span"):addClass("kr-tip-title"):wikitext(title) end | |||
if not (isempty(title) or isempty(desc)) then tip:tag("span"):addClass("kr-tip-sep") end | |||
if not isempty(desc) then tip:tag("span"):addClass("kr-tip-desc"):wikitext(desc) end | |||
end | |||
if icon_file then | |||
box:tag("div"):addClass("kr-hc-adv-icon") | |||
:wikitext(fileWikitext(icon_file, {size="64x64px", link="", lazy=true})) | |||
end | end | ||
local main = box:tag("div | local main = box:tag("div") | ||
local lab = main:tag("div"):addClass("kr-hc-adv-label"):wikitext(L.awakening) | local lab = main:tag("div"):addClass("kr-hc-adv-label"):wikitext(L.awakening) | ||
if not showLabels then lab:addClass("is-hidden") end | if not showLabels then lab:addClass("is-hidden") end | ||
main:tag("div"):addClass("kr-hc-adv-value"):wikitext( | main:tag("div"):addClass("kr-hc-adv-value"):wikitext(not isempty(display_val) and display_val or title or "") | ||
end | |||
-- WM: zweite Überschrift + Fähigkeit-Kachel ohne Icon/Tooltip | |||
if entity=="wm" and not isempty(data.ability) then | |||
col:tag("h3"):addClass("kr-hc-h"):wikitext(abilityLabel) | |||
local b2 = col:tag("div"):addClass("kr-hc-awakening kr-hc-adv kr-no-icon") | |||
local m2 = b2:tag("div") | |||
local lab2 = m2:tag("div"):addClass("kr-hc-adv-label"):wikitext(abilityLabel) | |||
if not showLabels then lab2:addClass("is-hidden") end | |||
m2:tag("div"):addClass("kr-hc-adv-value"):wikitext(data.ability) | |||
end | end | ||
end | end | ||
| Zeile 292: | Zeile 314: | ||
-- ======= RENDER ======= | -- ======= RENDER ======= | ||
function p.render(frame) | function p.render(frame) | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local name = pick(args.name, args[1]) or "" | local name = pick(args.name, args[1]) or "" | ||
local heroes = | local heroes = loadHeroes() | ||
local data = (heroes and (heroes[name] or heroes[mw.ustring.gsub(name or "", "_", " ")])) or {} | local data = (heroes and (heroes[name] or heroes[mw.ustring.gsub(name or "", "_", " ")])) or {} | ||
local UI = loadI18n().ui or {} | |||
local showLabels = norm(args.show_labels) == "yes" or norm(args.show_labels) == "1" | local showLabels = norm(args.show_labels) == "yes" or norm(args.show_labels) == "1" | ||
-- | -- Entity (nur eine Quelle) | ||
local entity = norm(pick(args.entity, data.entity)) | local entity = norm(pick(args.entity, data.entity)) | ||
if entity=="" then entity = "hero" end | |||
local isGuardian = (entity=="guardian") | |||
local isWM = (entity=="wm") | |||
local isMercenary = (entity=="mercenary") | |||
local isWM = (entity == "wm") | local isGod = (entity=="god") | ||
local isMercenary = (entity == "mercenary") | |||
local isGod = (entity == "god" | |||
-- Root | -- Root | ||
local root = mw.html.create("div"):addClass("kr-hero-card kr-layout") | local root = mw.html.create("div"):addClass("kr-hero-card kr-layout") | ||
root:addClass(isGuardian and "is-guardian" or isWM and "is-wm" or isMercenary and "is-mercenary" or isGod and "is-god" or "is-hero") | |||
if showLabels then root:addClass("kr-show-labels") end | if showLabels then root:addClass("kr-show-labels") end | ||
-- Labels | -- Basics-Labels: Defaults + optionale Overrides pro Entity | ||
local | local Bdef = UI.basics or {} | ||
local | local Bovr = (UI.basics_override and UI.basics_override[entity]) or {} | ||
local | local B = { | ||
class = pick(Bovr.class, Bdef.class), | |||
specialization = pick(Bovr.specialization, Bdef.specialization), | |||
attackstyle = pick(Bovr.attackstyle, Bdef.attackstyle), | |||
resource = pick(Bovr.resource, Bdef.resource), | |||
} | |||
-- | -- Dritte-Spalte-Labels (Awakening/Aura/WM): Default + optionale Overrides pro Entity | ||
local | local Ldef = (UI.labels and UI.labels.default) or {} | ||
local | local Lover = (UI.labels and UI.labels[entity]) or {} | ||
local | local L = { | ||
awakening = pick(Lover.awakening, Ldef.awakening), | |||
ability = pick(Lover.ability, Ldef.ability), -- nur für WM relevant | |||
} | |||
local | local class = tr("class", data.class) | ||
local attackstyle = tr("attackstyle", data.attackstyle) | |||
local specialization = tr("specialization", data.specialization) | |||
local resource = tr("resource", data.resource) | |||
local | |||
local | |||
-- Config | |||
local CFGmod = loadConfig() | local CFGmod = loadConfig() | ||
local icon_map = CFGmod.icon_map or {} | |||
local profile = pickProfile(CFGmod, entity) | local profile = pickProfile(CFGmod, entity) | ||
-- | -- Avatar | ||
local | local linkto = (isWM and "") or pick(args.linkto, name) | ||
local avatar_file = defaultAvatarFile(name, data, args) | |||
local | |||
local avatarDiv = root:tag("div"):addClass("kr-hc-avatar") | local avatarDiv = root:tag("div"):addClass("kr-hc-avatar") | ||
avatarDiv:wikitext( buildAvatar(name, linkto, | avatarDiv:wikitext( buildAvatar(name, linkto, avatar_file, data.skins, { isGuardian = isGuardian }) ) | ||
-- Content | -- Content | ||
local content = root:tag("div"):addClass("kr-hc-content") | local content = root:tag("div"):addClass("kr-hc-content") | ||
-- Header | -- Header | ||
local header = content:tag("div"):addClass("kr-hc-header") | local header = content:tag("div"):addClass("kr-hc-header") | ||
if not isempty(name) then | if not isempty(name) then | ||
header:tag("h2"):addClass("kr-hc-name"):wikitext(string.format("[[%s|%s]]", | local h = header:tag("h2"):addClass("kr-hc-name") | ||
if isWM then | |||
h:wikitext(name) -- WM keine Linkseite | |||
else | |||
local titleTarget = pick(args.linkto, name) | |||
h:wikitext(string.format("[[%s|%s]]", titleTarget, name)) | |||
end | |||
end | |||
local unlock_text = unlock_text(UI, data.unlock_at) | |||
if unlock_text and unlock_text~="" then | |||
header:tag("div"):addClass("kr-hc-unlockline"):wikitext("🔒 " .. unlock_text) | |||
end | end | ||
content:tag("div"):addClass("kr-hc-sep") | content:tag("div"):addClass("kr-hc-sep") | ||
-- Basics 2×2 | -- Basics 2×2 | ||
local basics = content:tag("div"):addClass("kr-hc-basics2") | local basics = content:tag("div"):addClass("kr-hc-basics2") | ||
local function pair(label, value) | local function pair(label, value) | ||
if isempty(value) then return end | if isempty(value) or isempty(label) then return end | ||
local it = basics:tag("div"):addClass("kr-hc-basic") | local it = basics:tag("div"):addClass("kr-hc-basic") | ||
it:tag("span"):addClass("label"):wikitext(label) | it:tag("span"):addClass("label"):wikitext(label) | ||
it:tag("span"):addClass("value"):wikitext(value) | it:tag("span"):addClass("value"):wikitext(value) | ||
end | end | ||
pair( | pair(B.class, class) | ||
pair( | pair(B.specialization, specialization) | ||
pair( | pair(B.attackstyle, attackstyle) | ||
pair( | pair(B.resource, resource) | ||
-- - | -- Haupt-Grid (Default) | ||
local grid_html = render_grid{ | |||
profile = profile, data = data, args = args, UI = UI, | |||
L = L, showLabels = showLabels, entity = entity, | |||
icon_map = icon_map, mode = "default", | |||
abilityLabel = (L.ability), | |||
} | |||
content:wikitext(grid_html) | |||
-- - | -- Chaos-Grid (nur Guardian und nur wenn Werte vorhanden) | ||
if isGuardian and type(data.chaos)=="table" then | |||
local hasChaos = false | |||
if profile.chaos_main_order and #profile.chaos_main_order>0 then | |||
local m = data.chaos.main or {} | |||
for _,k in ipairs(profile.chaos_main_order) do if not isempty(m[k]) then hasChaos=true break end end | |||
end | |||
if not hasChaos and profile.chaos_adv_order and #profile.chaos_adv_order>0 then | |||
local a = data.chaos.adv or {} | |||
for _,k in ipairs(profile.chaos_adv_order) do if not isempty(a[k]) then hasChaos=true break end end | |||
end | end | ||
if not hasChaos and (not isempty(data.chaos.aura_id) or not isempty(data.chaos.aura_value)) then | |||
hasChaos = true | |||
end | end | ||
if hasChaos then | |||
local chaosTitle = (UI.headers and UI.headers.chaos) or "" | |||
if chaosTitle~="" then content:tag("h2"):wikitext(chaosTitle) end | |||
content:tag("div"):addClass("kr-hc-sep") | |||
local chaos_html = render_grid{ | |||
profile = profile, data = data, L = L, UI = UI, | |||
showLabels = showLabels, entity = entity, | |||
icon_map = icon_map, mode = "chaos", | |||
} | |||
content:wikitext(chaos_html) | |||
end | |||
end | end | ||
return tostring(root) | return tostring(root) | ||
end | end | ||
p.main = p.render | p.main = p.render | ||
return p | return p | ||
Aktuelle Version vom 17. Oktober 2025, 07:46 Uhr
Die Dokumentation für dieses Modul kann unter Modul:HeroCard/Doku erstellt werden
-- Modul:HeroCard
local p = {}
-- ===== Helpers =====
local function isempty(v) return v == nil or v == "" end
local function pick(...) local n=select('#',...); for i=1,n do local v=select(i,...); if not isempty(v) then return v end end end
local function norm(s) if isempty(s) then return "" end return mw.ustring.lower(mw.text.trim(s)):gsub("%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
-- Lazy load
local CFG, I18N
local function loadConfig()
if CFG then return CFG end
for _,t in ipairs{ "Modul:HeroConfig", "Module:HeroConfig" } do
local ok, mod = pcall(require, t); if ok and type(mod)=="table" then CFG=mod; return CFG end
end
error("HeroConfig not found")
end
local function loadI18n()
if I18N then return I18N end
for _,t in ipairs{ "Modul:HeroI18n", "Module:HeroI18n" } do
local ok, mod = pcall(require, t); if ok and type(mod)=="table" then I18N=mod; return I18N end
end
I18N = { i18n={}, aw_desc={}, ui={} }; return I18N
end
-- HeroData
local function loadHeroes()
for _,t in ipairs{ "Modul:HeroData", "Module:HeroData" } do
local ok, mod = pcall(require, t)
if ok and type(mod)=="table" and type(mod.heroes)=="table" then
return mod.heroes
end
end
return nil
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
return string.format("[[%s]]", table.concat(parts, "|"))
end
-- Übersetzen (HeroI18n.i18n[kind][key_en])
local function tr(kind, value)
if isempty(value) then return value end
local map = loadI18n().i18n[kind] or {}
return map[norm(value)] or value
end
-- Tooltip-Text (aw_desc[key_en])
local function aw_desc(key_en)
if isempty(key_en) then return nil end
return (loadI18n().aw_desc or {})[norm(key_en)]
end
-- Unlock-Text über I18n
local function unlock_text(ui, raw)
if isempty(raw) then return nil end
local t = norm(raw)
local n = t:match("stage%s*(%d+)")
if n and ui.unlock and ui.unlock.at_stage then
return (ui.unlock.at_stage:gsub("%$1", n))
end
if t:match("pirate%s*ship") and ui.unlock and ui.unlock.pirate_ship then
return ui.unlock.pirate_ship
end
return raw
end
-- Default-Avatar
local function defaultAvatarFile(name, data, args)
if not isempty(args.avatar) then return args.avatar end
if not isempty(data.default_avatar) then return data.default_avatar end
if not isempty(data.default_skin) then
return string.format("%s_%s.png", name, data.default_skin)
end
if not isempty(name) then return name .. ".png" end
return nil
end
-- Avatar/Skins → TabberNeue
local function buildAvatar(name, linkto, defaultFile, skinsTable, opts)
opts = opts or {}
local isGuardian = opts.isGuardian == true
if type(skinsTable) ~= "table" or next(skinsTable) == nil then
return fileWikitext(defaultFile, { class="kr-hc-avatar-img", link=linkto, lazy=true })
end
local frame = mw.getCurrentFrame()
local defaultLabel = (loadI18n().ui and loadI18n().ui.default_skin) or ""
local panels = {}
local function push(label, file)
table.insert(panels,
tostring(label) .. "=\n" ..
fileWikitext(file, { class="kr-hc-avatar-img", link=linkto, lazy=true })
)
end
if isGuardian then
push("E1", defaultFile)
else
push(defaultLabel, defaultFile)
end
if skinsTable[1] ~= nil then
local idx = 1
for _, v in ipairs(skinsTable) do
local label, file
if type(v) == "table" then
label = v.label or v[1]
file = v.file or (name .. "_" .. (v.filename or label) .. ".png")
else
label = tostring(v)
file = name .. "_" .. label .. ".png"
end
idx = idx + 1
push(isGuardian and ("E"..idx) or label, file)
end
else
local tmp, idx = {}, 1
for k, v in pairs(skinsTable) do
local label, file
if type(v) == "table" then
label = v.label or v[1] or tostring(k)
file = v.file or (name .. "_" .. (v.filename or label) .. ".png")
else
label = tostring(v)
file = name .. "_" .. label .. ".png"
end
table.insert(tmp, {label=label, file=file})
end
table.sort(tmp, function(a,b) return tostring(a.label) < tostring(b.label) end)
for _,it in ipairs(tmp) do
idx = idx + 1
push(isGuardian and ("E"..idx) or it.label, it.file)
end
end
local content = table.concat(panels, "\n|-|\n")
return frame:extensionTag('tabber', content)
end
-- Profile-Auswahl
local function pickProfile(cfg, entity)
local profiles = cfg.profiles or {}
local base = profiles.default or {}
local over = profiles[entity] or {}
local P = {}
P.main_order = over.main_order or base.main_order or {}
P.main_meta = over.main_meta or base.main_meta or {}
P.adv_order = over.adv_order or base.adv_order or {}
P.adv_meta = over.adv_meta or base.adv_meta or {}
-- Guardian: Chaos
P.chaos_main_order = over.chaos_main_order or {}
P.chaos_main_meta = over.chaos_main_meta or {}
P.chaos_adv_order = over.chaos_adv_order or {}
P.chaos_adv_meta = over.chaos_adv_meta or {}
return P
end
-- Tooltip-HTML
local function addTooltip(box, title, desc)
if (isempty(title) and isempty(desc)) then return end
local tip = box:tag("span"):addClass("kr-tipbox")
if not isempty(title) then tip:tag("span"):addClass("kr-tip-title"):wikitext(title) end
if not (isempty(title) or isempty(desc)) then tip:tag("span"):addClass("kr-tip-sep") end
if not isempty(desc) then tip:tag("span"):addClass("kr-tip-desc"):wikitext(desc) end
end
-- Kleine Bausteine: Hauptattribute-Kachel
local function pill(parent, label, val, icon, showLabels, desc)
if isempty(val) then return end
local box = parent:tag("div"):addClass("kr-hc-pill kr-tip")
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")
local lab = text:tag("div"):addClass("kr-hc-pill-label"):wikitext(label or "")
if not showLabels then lab:addClass("is-hidden") end
text:tag("div"):addClass("kr-hc-pill-value"):wikitext(val)
addTooltip(box, label, desc)
end
-- Kleine Bausteine: Fortschritts-Kachel
local function adv(parent, label, val, icon, showLabels, desc)
if isempty(val) then return end
local box = parent:tag("div"):addClass("kr-hc-adv kr-tip")
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")
local lab = main:tag("div"):addClass("kr-hc-adv-label"):wikitext(label or "")
if not showLabels then lab:addClass("is-hidden") end
main:tag("div"):addClass("kr-hc-adv-value"):wikitext(val)
addTooltip(box, label, desc)
end
-- 3er-Grid Renderer
local function render_grid(opts)
local profile, data, L, UI, showLabels, entity = opts.profile, opts.data, opts.L, opts.UI, opts.showLabels, opts.entity
local mode = opts.mode or "default" -- "default" | "chaos"
local abilityLabel = (UI.labels and UI.labels.wm and UI.labels.wm.ability) or ""
local H_main = (UI.headers and UI.headers.main) or ""
local H_adv = (UI.headers and UI.headers.adv) or ""
local grid = mw.html.create("div"):addClass("kr-g3")
-- Spalte 1: Hauptattribute
do
local col = grid:tag("div"):addClass("kr-quad")
col:tag("h3"):addClass("kr-hc-h"):wikitext(H_main)
local mains = col:tag("div"):addClass("kr-hc-main-attrs")
local src = (mode=="chaos") and ((type(data.chaos)=="table" and (data.chaos.main or {})) or {}) or (data.main or {})
local order = (mode=="chaos") and profile.chaos_main_order or profile.main_order
local metaT = (mode=="chaos") and profile.chaos_main_meta or profile.main_meta
for _, key in ipairs(order) do
local meta = metaT[key] or { label = key }
local val = src[key]
if not isempty(val) then
pill(mains, meta.label, val, meta.icon, showLabels, meta.desc)
end
end
end
-- Spalte 2: Fortschrittsattribute
do
local col = grid:tag("div"):addClass("kr-quad")
col:tag("h3"):addClass("kr-hc-h"):wikitext(H_adv)
local advs = col:tag("div"):addClass("kr-hc-adv-attrs")
local src = (mode=="chaos") and ((type(data.chaos)=="table" and (data.chaos.adv or {})) or {}) or (data.adv or {})
local order = (mode=="chaos") and profile.chaos_adv_order or profile.adv_order
local metaT = (mode=="chaos") and profile.chaos_adv_meta or profile.adv_meta
for _, key in ipairs(order) do
local meta = metaT[key] or { label = key }
local val = src[key]
if not isempty(val) then
adv(advs, meta.label, val, meta.icon, showLabels, meta.desc)
end
end
end
-- Spalte 3: Awakening / Aura / Milestone + (WM) Fähigkeit
do
local col = grid:tag("div"):addClass("kr-quad")
col:tag("h3"):addClass("kr-hc-h"):wikitext(L.awakening)
local id_en, display_val
if mode=="chaos" and entity=="guardian" then
id_en = type(data.chaos)=="table" and data.chaos.aura_id or nil
display_val= type(data.chaos)=="table" and data.chaos.aura_value or nil
else
if entity=="guardian" then
id_en = data.aura_id
display_val = data.aura_value
elseif entity=="wm" then
id_en = data.milestone_id
else
id_en = data.awakening_id
end
end
local icon_map = ((opts.icon_map or {}).awakening_bonus) or {}
local icon_file= icon_map[norm(id_en)]
local title = tr("awakening_bonus", id_en)
local desc = aw_desc(id_en)
if not (isempty(title) and isempty(display_val)) then
local box = col:tag("div"):addClass("kr-hc-awakening kr-hc-adv kr-tip")
if not (isempty(title) and isempty(desc)) then
local tip = box:tag("span"):addClass("kr-tipbox")
if not isempty(title) then tip:tag("span"):addClass("kr-tip-title"):wikitext(title) end
if not (isempty(title) or isempty(desc)) then tip:tag("span"):addClass("kr-tip-sep") end
if not isempty(desc) then tip:tag("span"):addClass("kr-tip-desc"):wikitext(desc) end
end
if icon_file then
box:tag("div"):addClass("kr-hc-adv-icon")
:wikitext(fileWikitext(icon_file, {size="64x64px", link="", lazy=true}))
end
local main = box:tag("div")
local lab = main:tag("div"):addClass("kr-hc-adv-label"):wikitext(L.awakening)
if not showLabels then lab:addClass("is-hidden") end
main:tag("div"):addClass("kr-hc-adv-value"):wikitext(not isempty(display_val) and display_val or title or "")
end
-- WM: zweite Überschrift + Fähigkeit-Kachel ohne Icon/Tooltip
if entity=="wm" and not isempty(data.ability) then
col:tag("h3"):addClass("kr-hc-h"):wikitext(abilityLabel)
local b2 = col:tag("div"):addClass("kr-hc-awakening kr-hc-adv kr-no-icon")
local m2 = b2:tag("div")
local lab2 = m2:tag("div"):addClass("kr-hc-adv-label"):wikitext(abilityLabel)
if not showLabels then lab2:addClass("is-hidden") end
m2:tag("div"):addClass("kr-hc-adv-value"):wikitext(data.ability)
end
end
return tostring(grid)
end
-- ======= RENDER =======
function p.render(frame)
local args = getArgs(frame)
local name = pick(args.name, args[1]) or ""
local heroes = loadHeroes()
local data = (heroes and (heroes[name] or heroes[mw.ustring.gsub(name or "", "_", " ")])) or {}
local UI = loadI18n().ui or {}
local showLabels = norm(args.show_labels) == "yes" or norm(args.show_labels) == "1"
-- Entity (nur eine Quelle)
local entity = norm(pick(args.entity, data.entity))
if entity=="" then entity = "hero" end
local isGuardian = (entity=="guardian")
local isWM = (entity=="wm")
local isMercenary = (entity=="mercenary")
local isGod = (entity=="god")
-- Root
local root = mw.html.create("div"):addClass("kr-hero-card kr-layout")
root:addClass(isGuardian and "is-guardian" or isWM and "is-wm" or isMercenary and "is-mercenary" or isGod and "is-god" or "is-hero")
if showLabels then root:addClass("kr-show-labels") end
-- Basics-Labels: Defaults + optionale Overrides pro Entity
local Bdef = UI.basics or {}
local Bovr = (UI.basics_override and UI.basics_override[entity]) or {}
local B = {
class = pick(Bovr.class, Bdef.class),
specialization = pick(Bovr.specialization, Bdef.specialization),
attackstyle = pick(Bovr.attackstyle, Bdef.attackstyle),
resource = pick(Bovr.resource, Bdef.resource),
}
-- Dritte-Spalte-Labels (Awakening/Aura/WM): Default + optionale Overrides pro Entity
local Ldef = (UI.labels and UI.labels.default) or {}
local Lover = (UI.labels and UI.labels[entity]) or {}
local L = {
awakening = pick(Lover.awakening, Ldef.awakening),
ability = pick(Lover.ability, Ldef.ability), -- nur für WM relevant
}
local class = tr("class", data.class)
local attackstyle = tr("attackstyle", data.attackstyle)
local specialization = tr("specialization", data.specialization)
local resource = tr("resource", data.resource)
-- Config
local CFGmod = loadConfig()
local icon_map = CFGmod.icon_map or {}
local profile = pickProfile(CFGmod, entity)
-- Avatar
local linkto = (isWM and "") or pick(args.linkto, name)
local avatar_file = defaultAvatarFile(name, data, args)
local avatarDiv = root:tag("div"):addClass("kr-hc-avatar")
avatarDiv:wikitext( buildAvatar(name, linkto, avatar_file, data.skins, { isGuardian = isGuardian }) )
-- Content
local content = root:tag("div"):addClass("kr-hc-content")
-- Header
local header = content:tag("div"):addClass("kr-hc-header")
if not isempty(name) then
local h = header:tag("h2"):addClass("kr-hc-name")
if isWM then
h:wikitext(name) -- WM keine Linkseite
else
local titleTarget = pick(args.linkto, name)
h:wikitext(string.format("[[%s|%s]]", titleTarget, name))
end
end
local unlock_text = unlock_text(UI, data.unlock_at)
if unlock_text and unlock_text~="" then
header:tag("div"):addClass("kr-hc-unlockline"):wikitext("🔒 " .. unlock_text)
end
content:tag("div"):addClass("kr-hc-sep")
-- Basics 2×2
local basics = content:tag("div"):addClass("kr-hc-basics2")
local function pair(label, value)
if isempty(value) or isempty(label) then return end
local it = basics:tag("div"):addClass("kr-hc-basic")
it:tag("span"):addClass("label"):wikitext(label)
it:tag("span"):addClass("value"):wikitext(value)
end
pair(B.class, class)
pair(B.specialization, specialization)
pair(B.attackstyle, attackstyle)
pair(B.resource, resource)
-- Haupt-Grid (Default)
local grid_html = render_grid{
profile = profile, data = data, args = args, UI = UI,
L = L, showLabels = showLabels, entity = entity,
icon_map = icon_map, mode = "default",
abilityLabel = (L.ability),
}
content:wikitext(grid_html)
-- Chaos-Grid (nur Guardian und nur wenn Werte vorhanden)
if isGuardian and type(data.chaos)=="table" then
local hasChaos = false
if profile.chaos_main_order and #profile.chaos_main_order>0 then
local m = data.chaos.main or {}
for _,k in ipairs(profile.chaos_main_order) do if not isempty(m[k]) then hasChaos=true break end end
end
if not hasChaos and profile.chaos_adv_order and #profile.chaos_adv_order>0 then
local a = data.chaos.adv or {}
for _,k in ipairs(profile.chaos_adv_order) do if not isempty(a[k]) then hasChaos=true break end end
end
if not hasChaos and (not isempty(data.chaos.aura_id) or not isempty(data.chaos.aura_value)) then
hasChaos = true
end
if hasChaos then
local chaosTitle = (UI.headers and UI.headers.chaos) or ""
if chaosTitle~="" then content:tag("h2"):wikitext(chaosTitle) end
content:tag("div"):addClass("kr-hc-sep")
local chaos_html = render_grid{
profile = profile, data = data, L = L, UI = UI,
showLabels = showLabels, entity = entity,
icon_map = icon_map, mode = "chaos",
}
content:wikitext(chaos_html)
end
end
return tostring(root)
end
p.main = p.render
return p