Zum Inhalt springen


Modul:Firestone: Unterschied zwischen den Versionen

Aus Firestone Idle RPG Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 4: Zeile 4:
-- ===== helpers =====
-- ===== helpers =====
local function isempty(v) return v==nil or v=="" end
local function isempty(v) return v==nil or v=="" end
local function norm(s)
local function norm(s) if s==nil then return "" end s=mw.text.trim(tostring(s)); return mw.ustring.lower(s):gsub("%s+"," ") end
  if s==nil then return "" end
  s = mw.text.trim(tostring(s))
  return mw.ustring.lower(s):gsub("%s+"," ")
end
-- bevorzugt die Argumente, die an die Vorlage übergeben wurden
local function getArgs(frame)
local function getArgs(frame)
   local a = {}
   local a = {}
   local p = frame:getParent()
   local p = frame:getParent()
   local src = (p and p.args) or frame.args or {}
   local src = (p and p.args) or frame.args or {}
   for k, v in pairs(src) do
   for k,v in pairs(src) do if v~=nil and v~="" then a[k]=mw.text.trim(tostring(v)) end end
    if v ~= nil and v ~= "" then
      a[k] = mw.text.trim(tostring(v))
    end
  end
   return a
   return a
end
end
local function pick_key(tbl, key)
local function pick_key(tbl, key)
   if type(tbl)~="table" then return nil end
   if type(tbl)~="table" then return nil end
   if tbl[key]~=nil then return key end
   if tbl[key]~=nil then return key end
   local k2 = tostring(key)
   local k2 = tostring(key); if tbl[k2]~=nil then return k2 end
  if tbl[k2]~=nil then return k2 end
   local kn = norm(k2); for k,_ in pairs(tbl) do if norm(k)==kn then return k end end
   local kn = norm(k2)
  for k,_ in pairs(tbl) do if norm(k)==kn then return k end end
   return nil
   return nil
end
end
Zeile 42: Zeile 30:
local function fileWikitext(file, size)
local function fileWikitext(file, size)
   if isempty(file) then return "" end
   if isempty(file) then return "" end
   local parts = {"Datei:"..file}
   local parts = {"Datei:"..file}; if not isempty(size) then table.insert(parts, size) end
  if not isempty(size) then table.insert(parts, size) end
   return string.format("[[%s]]", table.concat(parts, "|"))
   return string.format("[[%s]]", table.concat(parts, "|"))
end
end
Zeile 49: Zeile 36:
-- ===== lazy loads =====
-- ===== lazy loads =====
local HEROES, EVENTS, I18N
local HEROES, EVENTS, I18N
local function heroes()
local function heroes()
   if HEROES then return HEROES end
   if HEROES then return HEROES end
   for _,t in ipairs{ "Module:HeroData", "Modul:HeroData" } do
   for _,t in ipairs{ "Module:HeroData","Modul:HeroData" } do
     local ok, data = pcall(mw.loadData, t)
     local ok,d = pcall(mw.loadData,t); if ok and type(d)=="table" and type(d.heroes)=="table" then HEROES=d.heroes;return HEROES end
    if ok and type(data)=="table" and type(data.heroes)=="table" then HEROES = data.heroes; return HEROES end
     local ok2,m=pcall(require,t); if ok2 and type(m)=="table" and type(m.heroes)=="table" then HEROES=m.heroes;return HEROES end
     local ok2, mod = pcall(require, t)
   end; HEROES={}; return HEROES
    if ok2 and type(mod)=="table" and type(mod.heroes)=="table" then HEROES = mod.heroes; return HEROES end
   end
  HEROES = {}; return HEROES
end
end
local function events()
local function events()
   if EVENTS then return EVENTS end
   if EVENTS then return EVENTS end
  -- erst loadData (empfohlen für reine Datenmodule), dann fallback require
   for _,t in ipairs{ "Module:EventData","Modul:EventData" } do
   for _,t in ipairs{ "Module:EventData", "Modul:EventData" } do
     local ok,d=pcall(mw.loadData,t); if ok and type(d)=="table" and type(d.events)=="table" then EVENTS=d.events;return EVENTS end
     local ok, data = pcall(mw.loadData, t)
    if ok and type(data)=="table" and type(data.events)=="table" then EVENTS = data.events; return EVENTS end
  end
  for _,t in ipairs{ "Module:EventData", "Modul:EventData" } do
    local ok, mod = pcall(require, t)
    if ok and type(mod)=="table" and type(mod.events)=="table" then EVENTS = mod.events; return EVENTS end
   end
   end
   EVENTS = {}; return EVENTS
   for _,t in ipairs{ "Module:EventData","Modul:EventData" } do
    local ok,m=pcall(require,t); if ok and type(m)=="table" and type(m.events)=="table" then EVENTS=m.events;return EVENTS end
  end; EVENTS={}; return EVENTS
end
end
local function i18n()
local function i18n()
   if I18N then return I18N end
   if I18N then return I18N end
   for _,t in ipairs{ "Module:HeroI18n", "Modul:HeroI18n" } do
   for _,t in ipairs{ "Module:HeroI18n","Modul:HeroI18n" } do
     local ok, data = pcall(mw.loadData, t)
     local ok,d=pcall(mw.loadData,t); if ok and type(d)=="table" then I18N=d;return I18N end
    if ok and type(data)=="table" then I18N = data; return I18N end
     local ok2,m=pcall(require,t);    if ok2 and type(m)=="table" then I18N=m;return I18N end
     local ok2, mod = pcall(require, t)
    if ok2 and type(mod)=="table" then I18N = mod; return I18N end
   end
   end
   I18N = { i18n={}, events={names={},sections={},infobox={},phrases={}} }
   I18N={ i18n={}, events={names={},sections={},infobox={},phrases={}} }; return I18N
  return I18N
end
end


-- ===== translations =====
-- ===== translations & helpers =====
local function tr(kind, v)
local function tr(kind, v) if isempty(v) then return v end local map=(i18n().i18n or {})[kind] or {}; return map[norm(v)] or v end
  if isempty(v) then return v end
local function tr_currency(k) return tr("currency",k) end
  local map=(i18n().i18n or {})[kind] or {}
local function tr_event_name(k) local names=(i18n().events or {}).names or {}; return names[norm(k)] or k end
  return map[norm(v)] or v
end
local function tr_currency(k) return tr("currency", k) end
local function tr_event_name(k)
  local names=(i18n().events or {}).names or {}
  return names[norm(k)] or k
end
local function section_label(evkey, which)
local function section_label(evkey, which)
   local E = i18n().events or {}
   local E=i18n().events or {}; local ev=(E.sections and E.sections[norm(evkey)]) or {}; local def=(E.sections and E.sections.default) or {}
  local ev = (E.sections and E.sections[norm(evkey)]) or {}
  local def= (E.sections and E.sections.default) or {}
   return (ev[which] or def[which] or "")
   return (ev[which] or def[which] or "")
end
end
 
local function month_tr(s) local map=((i18n().i18n or {}).month) or {}; return map[norm(s or "")] or (s or "") end
-- ===== dispatch helpers =====
local function format_unlocked(raw)
local function resolve_event(key)
   local P=(i18n().events or {}).phrases or {}; local tpl=P.character_level or "Level $1"
   local evs = events()
   local n=tostring(raw or ""):match("^character_level_(%d+)$"); if n then return (tpl:gsub("%$1",n)) end
   local real = pick_key(evs, key)
   return raw or ""
   return real and evs[real] or nil
end
end
local function resolve_event(key) local evs=events(); local real=pick_key(evs,key); return real and evs[real] or nil end


-- ===== feature: currency =====
-- Drop-Block rendern
-- {{Firestone|Currency|Gold}}
local function render_drop(ev)
local function fn_currency(args)
   local D=ev.drop or {}; local P=(i18n().events or {}).phrases or {}; local token_key=ev.token and ev.token.key or ""; local lines={}
   local key = args[2]; if isempty(key) then return "" end
   if D.base and D.base.amount and D.base.period then
  return tostring( tr_currency(key) or "" )
    table.insert(lines, "* "..(P.drop_rate_base or "Drop-Rate: $1 $2 alle $3 (Basis).")
end
      :gsub("%$1",tostring(D.base.amount)):gsub("%$2",tr_currency(token_key)):gsub("%$3",tostring(D.base.period)))
 
  end
-- ===== feature: event data =====
  if D.boost and D.boost.amount and D.boost.period then
-- {{Firestone|Event|Halloween|field|...}}
    table.insert(lines, "* "..(P.drop_rate_boost or "Drop-Rate: $1 $2 alle $3 (nach Kauf).")
local function fn_event(args)
      :gsub("%$1",tostring(D.boost.amount)):gsub("%$2",tr_currency(token_key)):gsub("%$3",tostring(D.boost.period)))
  local key = args[2]; if isempty(key) then return "" end
   end
  local ev = resolve_event(key); if not ev then return "" end
   if D.base_daily_gain then
   if not args[3] then return "" end
     table.insert(lines, "* "..(P.fixed_daily_gain or "Fester Zuwachs: $1 $2 pro Tag.")
  local path, i = {}, 3
      :gsub("%$1",tostring(D.base_daily_gain)):gsub("%$2",tr_currency(token_key)))
  while args[i] do table.insert(path, args[i]); i=i+1 end
   end
  local v = deep_get(ev, path)
  if D.offline_cap then
  return tostring(v or "")
     table.insert(lines, "* "..(P.offline_cap or "Offline-Gewinne sind auf $1 begrenzt.")
end
      :gsub("%$1",tostring(D.offline_cap)))
 
-- {{Firestone|EventName|Halloween}}  (nur Titel aus I18n)
local function fn_event_name(args)
  local key = args[2]; if isempty(key) then return "" end
  return tostring(tr_event_name(key))
end
 
-- {{Firestone|EventLabel|Halloween|hub}} → "Hexenhütte"
-- optional {{Firestone|EventLabel|Halloween|name}} (wenn du Namenslabel über sections lösen willst)
local function fn_event_label(args)
  local key, which = args[2], args[3]
  if isempty(key) or isempty(which) then return "" end
  return section_label(key, which)
end
 
-- {{Firestone|EventInfobox|Halloween|type}} → "Kalenderevent"
-- {{Firestone|EventInfobox|Halloween|unlocks_at}} → Label "Freigeschaltet bei"
local function fn_event_infobox(args)
  local key, which = args[2], args[3]
  if isempty(key) or isempty(which) then return "" end
   local E = i18n().events or {}
   if which == "type" then
     local ev = resolve_event(key); if not ev then return "" end
    local t = ev.type
    local map = (E.infobox and E.infobox.type) or {}
    return tostring(map and map[norm(t or "")] or (t or ""))
   else
     local lbl = (E.infobox and E.infobox[which]) or ""
    return tostring(lbl or "")
   end
   end
  return table.concat(lines,"\n")
end
end


-- {{Firestone|EventPhrases|offline_cap|24h}}
-- Avatartabellen
local function fn_event_phrase(args)
local function render_avatar_year(ev, year)
  local phkey, arg1, arg2, arg3 = args[2], args[3], args[4], args[5]
  if isempty(phkey) then return "" end
  local P = (i18n().events or {}).phrases or {}
  local tpl = P[phkey]; if isempty(tpl) then return "" end
  local s = tpl
  s = s:gsub("%$1", tostring(arg1 or "")):gsub("%$2", tostring(arg2 or "")):gsub("%$3", tostring(arg3 or ""))
  return s
end
 
-- {{Firestone|EventAvatars|Halloween|2024}} → Tabelle
local function fn_event_avatars(args)
  local key, year = args[2], tonumber(args[3] or "")
  if isempty(key) or not year then return "" end
  local ev = resolve_event(key); if not ev then return "" end
   local list = ev.avatars and ev.avatars[year]; if type(list)~="table" then return "" end
   local list = ev.avatars and ev.avatars[year]; if type(list)~="table" then return "" end
 
   local token_key=ev.token and ev.token.key or ""; local token_icon=ev.token and ev.token.icon or ""
   local token_key = ev.token and ev.token.key or ""
   local out={}
  local token_icon= ev.token and ev.token.icon or ""
   local out = {}
   table.insert(out, '{| class="article-table" style="font-size:14px;"')
   table.insert(out, '{| class="article-table" style="font-size:14px;"')
   table.insert(out, '! colspan="2" | Avatar\n! Preis')
   table.insert(out, '! colspan="2" | Avatar\n! Preis')
Zeile 188: Zeile 109:
     table.insert(out, '|-')
     table.insert(out, '|-')
     table.insert(out, string.format('|%s || %s || %s %s %s',
     table.insert(out, string.format('|%s || %s || %s %s %s',
       fileWikitext(it.file, "50px"),
       fileWikitext(it.file,"50px"), it.title or "",
      it.title or "",
       fileWikitext(token_icon,"25px"), tostring(it.cost or 0), tr_currency(token_key)))
       fileWikitext(token_icon, "25px"),
      tostring(it.cost or 0),
      tr_currency(token_key)
    ))
   end
   end
   table.insert(out, '|}')
   table.insert(out, '|}')
Zeile 199: Zeile 116:
end
end


-- {{Firestone|EventHistory|Halloween}}
local function render_avatars_all(ev)
local function fn_event_history(args)
   local A = ev.avatars or {}; local years={}
   local key = args[2]; if isempty(key) then return "" end
  for y,_ in pairs(A) do table.insert(years, tonumber(y) or y) end
   local ev = resolve_event(key); if not ev then return "" end
  table.sort(years)
   local H = ev.history or {}; if #H==0 then return "" end
   local out={}
   local P = (i18n().events or {}).phrases or {}
  for _,y in ipairs(years) do
  local alt_on_gp = P.alt_platform or "auf Google Play"
    table.insert(out, string.format("==== %s ====\n%s", tostring(y), render_avatar_year(ev, y)))
   local lines = {}
  end
  return table.concat(out, "\n")
end
 
-- History
local function render_history(ev)
   local H=ev.history or {}; if #H==0 then return "" end
   local P=(i18n().events or {}).phrases or {}; local alt_on_gp=P.alt_platform or "auf Google Play"
   local lines={}
   for _,h in ipairs(H) do
   for _,h in ipairs(H) do
     local base = string.format("* %s: %s bis %s", tostring(h.year or ""), tostring(h.start or ""), tostring(h['end'] or ""))
     local base = string.format("* %s: %s bis %s", tostring(h.year or ""), tostring(h.start or ""), tostring(h['end'] or ""))
Zeile 215: Zeile 140:
end
end


-- ===== feature: hero =====
-- ===== feature: Event (einheitlich) =====
-- {{Firestone|Hero|Talia|main|damage}}
-- {{Firestone|Event|halloween|...}}
-- Alias: {{Firestone|Talia|main|damage}} (wenn 1. Param direkt ein Held ist)
local function fn_event(args)
local function fn_hero(args)
   local key=args[2]; if isempty(key) then return "" end
   local name = args[2]; if isempty(name) then return "" end
   local ev=resolve_event(key); if not ev then return "" end
   local H = heroes()
   if not args[3] then return "" end
   local real = pick_key(H, name)
 
   if not real then return "" end
  -- Labels (Pseudo-Namespace)
   local hero = H[real]; if not hero then return "" end
   if args[3]=="@infobox" and args[4] then
    local E=i18n().events or {}; local lbl=(E.infobox and E.infobox[args[4]]) or ""; return tostring(lbl or "")
  end
   if args[3]=="@section" and args[4] then
    return section_label(key, args[4])
  end
 
  -- Pfad/Key
   local path, i = {}, 3
   local path, i = {}, 3
   while args[i] do table.insert(path, args[i]); i=i+1 end
   while args[i] do table.insert(path,args[i]); i=i+1 end
  local v = deep_get(hero, path)
   local last = norm(path[#path] or "")
   local last = norm(path[#path] or "")
   if last=="class" then return tr("class", v) or ""
 
   elseif last=="attackstyle" then return tr("attackstyle", v) or ""
  -- bequemer Alias
   elseif last=="specialization" then return tr("specialization", v) or ""
  if last=="month" then path[#path]="date"; last="date" end
   elseif last=="resource" then return tr("resource", v) or ""
  if last=="name"  then return tr_event_name(key) or section_label(key,"name") end
  if last=="history" then return render_history(ev) end
  if last=="avatars" then
    local year = tonumber(path[4] or "")
    if year then return render_avatar_year(ev, year) else return render_avatars_all(ev) end
  end
  if last=="drop" then return render_drop(ev) end
 
  -- normaler Wert
  local v = deep_get(ev, path)
 
  if last=="date" then
    v = month_tr(v)
  elseif last=="unlocks_at" then
    v = format_unlocked(v)
  elseif last=="type" then
    local map=((i18n().events or {}).infobox or {}).type or {}; v = map[norm(v or "")] or (v or "")
  end
 
  if type(v)=="string" then v=mw.text.trim(v) end
  return tostring(v or "")
end
 
-- ===== feature: currency / hero (bleibt) =====
local function fn_currency(args) local key=args[2]; if isempty(key) then return "" end; return tostring(tr_currency(key) or "") end
local function fn_hero(args)
  local name=args[2]; if isempty(name) then return "" end
  local H=heroes(); local real=pick_key(H,name); if not real then return "" end
  local hero=H[real]; if not hero then return "" end
  local path,i={},3; while args[i] do table.insert(path,args[i]); i=i+1 end
  local v=deep_get(hero,path); local last=norm(path[#path] or "")
   if last=="class" then return tr("class",v) or ""
   elseif last=="attackstyle" then return tr("attackstyle",v) or ""
   elseif last=="specialization" then return tr("specialization",v) or ""
   elseif last=="resource" then return tr("resource",v) or ""
   else return tostring(v or "") end
   else return tostring(v or "") end
end
end
Zeile 237: Zeile 202:
-- ===== main dispatcher =====
-- ===== main dispatcher =====
function M.main(frame)
function M.main(frame)
   local a = getArgs(frame)
   local a=getArgs(frame); for i=1,10 do a[i]=a[i] and tostring(a[i]) or nil end
  for i=1,10 do a[i] = a[i] and tostring(a[i]) or nil end
   local dom=a[1] and norm(a[1]) or ""
   local dom = a[1] and norm(a[1]) or ""
   if dom=="event"     then return fn_event(a)
 
   elseif dom=="currency" then return fn_currency(a)
   if dom=="currency" then return fn_currency(a)
   elseif dom=="hero"     then return fn_hero(a)
  elseif dom=="event" then return fn_event(a)
   else return "" end
   elseif dom=="eventname" or dom=="event_name" then return fn_event_name(a)
  elseif dom=="eventlabel" or dom=="event_label" then return fn_event_label(a)
  elseif dom=="eventinfobox" or dom=="event_infobox" then return fn_event_infobox(a)
  elseif dom=="eventphrases" or dom=="event_phrase" then return fn_event_phrase(a)
  elseif dom=="eventavatars" or dom=="event_avatars" then return fn_event_avatars(a)
  elseif dom=="eventhistory" or dom=="event_history" then return fn_event_history(a)
   elseif dom=="hero" then return fn_hero(a)
   else
    -- Fallback: nichts passendes → leer
    return ""
  end
end
end


return M
return M

Version vom 19. Oktober 2025, 16:25 Uhr

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

-- Modul:Firestone
local M = {}

-- ===== helpers =====
local function isempty(v) return v==nil or v=="" end
local function norm(s) if s==nil then return "" end s=mw.text.trim(tostring(s)); return mw.ustring.lower(s):gsub("%s+"," ") end
local function getArgs(frame)
  local a = {}
  local p = frame:getParent()
  local src = (p and p.args) or frame.args or {}
  for k,v in pairs(src) do if v~=nil and v~="" then a[k]=mw.text.trim(tostring(v)) end end
  return a
end
local function pick_key(tbl, key)
  if type(tbl)~="table" then return nil end
  if tbl[key]~=nil then return key end
  local k2 = tostring(key); if tbl[k2]~=nil then return k2 end
  local kn = norm(k2); for k,_ in pairs(tbl) do if norm(k)==kn then return k end end
  return nil
end
local function deep_get(tbl, path)
  local cur = tbl
  for _,k in ipairs(path) do
    if type(cur)~="table" then return nil end
    local real = pick_key(cur, k); if not real then return nil end
    cur = cur[real]
  end
  return cur
end
local function fileWikitext(file, size)
  if isempty(file) then return "" end
  local parts = {"Datei:"..file}; if not isempty(size) then table.insert(parts, size) end
  return string.format("[[%s]]", table.concat(parts, "|"))
end

-- ===== lazy loads =====
local HEROES, EVENTS, I18N
local function heroes()
  if HEROES then return HEROES end
  for _,t in ipairs{ "Module:HeroData","Modul:HeroData" } do
    local ok,d = pcall(mw.loadData,t); if ok and type(d)=="table" and type(d.heroes)=="table" then HEROES=d.heroes;return HEROES end
    local ok2,m=pcall(require,t); if ok2 and type(m)=="table" and type(m.heroes)=="table" then HEROES=m.heroes;return HEROES end
  end; HEROES={}; return HEROES
end
local function events()
  if EVENTS then return EVENTS end
  for _,t in ipairs{ "Module:EventData","Modul:EventData" } do
    local ok,d=pcall(mw.loadData,t); if ok and type(d)=="table" and type(d.events)=="table" then EVENTS=d.events;return EVENTS end
  end
  for _,t in ipairs{ "Module:EventData","Modul:EventData" } do
    local ok,m=pcall(require,t); if ok and type(m)=="table" and type(m.events)=="table" then EVENTS=m.events;return EVENTS end
  end; EVENTS={}; return EVENTS
end
local function i18n()
  if I18N then return I18N end
  for _,t in ipairs{ "Module:HeroI18n","Modul:HeroI18n" } do
    local ok,d=pcall(mw.loadData,t); if ok and type(d)=="table" then I18N=d;return I18N end
    local ok2,m=pcall(require,t);    if ok2 and type(m)=="table" then I18N=m;return I18N end
  end
  I18N={ i18n={}, events={names={},sections={},infobox={},phrases={}} }; return I18N
end

-- ===== translations & helpers =====
local function tr(kind, v) if isempty(v) then return v end local map=(i18n().i18n or {})[kind] or {}; return map[norm(v)] or v end
local function tr_currency(k) return tr("currency",k) end
local function tr_event_name(k) local names=(i18n().events or {}).names or {}; return names[norm(k)] or k end
local function section_label(evkey, which)
  local E=i18n().events or {}; local ev=(E.sections and E.sections[norm(evkey)]) or {}; local def=(E.sections and E.sections.default) or {}
  return (ev[which] or def[which] or "")
end
local function month_tr(s) local map=((i18n().i18n or {}).month) or {}; return map[norm(s or "")] or (s or "") end
local function format_unlocked(raw)
  local P=(i18n().events or {}).phrases or {}; local tpl=P.character_level or "Level $1"
  local n=tostring(raw or ""):match("^character_level_(%d+)$"); if n then return (tpl:gsub("%$1",n)) end
  return raw or ""
end
local function resolve_event(key) local evs=events(); local real=pick_key(evs,key); return real and evs[real] or nil end

-- Drop-Block rendern
local function render_drop(ev)
  local D=ev.drop or {}; local P=(i18n().events or {}).phrases or {}; local token_key=ev.token and ev.token.key or ""; local lines={}
  if D.base and D.base.amount and D.base.period then
    table.insert(lines, "* "..(P.drop_rate_base or "Drop-Rate: $1 $2 alle $3 (Basis).")
      :gsub("%$1",tostring(D.base.amount)):gsub("%$2",tr_currency(token_key)):gsub("%$3",tostring(D.base.period)))
  end
  if D.boost and D.boost.amount and D.boost.period then
    table.insert(lines, "* "..(P.drop_rate_boost or "Drop-Rate: $1 $2 alle $3 (nach Kauf).")
      :gsub("%$1",tostring(D.boost.amount)):gsub("%$2",tr_currency(token_key)):gsub("%$3",tostring(D.boost.period)))
  end
  if D.base_daily_gain then
    table.insert(lines, "* "..(P.fixed_daily_gain or "Fester Zuwachs: $1 $2 pro Tag.")
      :gsub("%$1",tostring(D.base_daily_gain)):gsub("%$2",tr_currency(token_key)))
  end
  if D.offline_cap then
    table.insert(lines, "* "..(P.offline_cap or "Offline-Gewinne sind auf $1 begrenzt.")
      :gsub("%$1",tostring(D.offline_cap)))
  end
  return table.concat(lines,"\n")
end

-- Avatartabellen
local function render_avatar_year(ev, year)
  local list = ev.avatars and ev.avatars[year]; if type(list)~="table" then return "" end
  local token_key=ev.token and ev.token.key or ""; local token_icon=ev.token and ev.token.icon or ""
  local out={}
  table.insert(out, '{| class="article-table" style="font-size:14px;"')
  table.insert(out, '! colspan="2" | Avatar\n! Preis')
  for _,it in ipairs(list) do
    table.insert(out, '|-')
    table.insert(out, string.format('|%s || %s || %s %s %s',
      fileWikitext(it.file,"50px"), it.title or "",
      fileWikitext(token_icon,"25px"), tostring(it.cost or 0), tr_currency(token_key)))
  end
  table.insert(out, '|}')
  return table.concat(out, "\n")
end

local function render_avatars_all(ev)
  local A = ev.avatars or {}; local years={}
  for y,_ in pairs(A) do table.insert(years, tonumber(y) or y) end
  table.sort(years)
  local out={}
  for _,y in ipairs(years) do
    table.insert(out, string.format("==== %s ====\n%s", tostring(y), render_avatar_year(ev, y)))
  end
  return table.concat(out, "\n")
end

-- History
local function render_history(ev)
  local H=ev.history or {}; if #H==0 then return "" end
  local P=(i18n().events or {}).phrases or {}; local alt_on_gp=P.alt_platform or "auf Google Play"
  local lines={}
  for _,h in ipairs(H) do
    local base = string.format("* %s: %s bis %s", tostring(h.year or ""), tostring(h.start or ""), tostring(h['end'] or ""))
    if h.alt_platform then base = base .. string.format(" (%s %s)", tostring(h.alt_platform), alt_on_gp) end
    table.insert(lines, base)
  end
  return table.concat(lines, "\n")
end

-- ===== feature: Event (einheitlich) =====
-- {{Firestone|Event|halloween|...}}
local function fn_event(args)
  local key=args[2]; if isempty(key) then return "" end
  local ev=resolve_event(key); if not ev then return "" end
  if not args[3] then return "" end

  -- Labels (Pseudo-Namespace)
  if args[3]=="@infobox" and args[4] then
    local E=i18n().events or {}; local lbl=(E.infobox and E.infobox[args[4]]) or ""; return tostring(lbl or "")
  end
  if args[3]=="@section" and args[4] then
    return section_label(key, args[4])
  end

  -- Pfad/Key
  local path, i = {}, 3
  while args[i] do table.insert(path,args[i]); i=i+1 end
  local last = norm(path[#path] or "")

  -- bequemer Alias
  if last=="month" then path[#path]="date"; last="date" end
  if last=="name"  then return tr_event_name(key) or section_label(key,"name") end
  if last=="history" then return render_history(ev) end
  if last=="avatars" then
    local year = tonumber(path[4] or "")
    if year then return render_avatar_year(ev, year) else return render_avatars_all(ev) end
  end
  if last=="drop" then return render_drop(ev) end

  -- normaler Wert
  local v = deep_get(ev, path)

  if last=="date" then
    v = month_tr(v)
  elseif last=="unlocks_at" then
    v = format_unlocked(v)
  elseif last=="type" then
    local map=((i18n().events or {}).infobox or {}).type or {}; v = map[norm(v or "")] or (v or "")
  end

  if type(v)=="string" then v=mw.text.trim(v) end
  return tostring(v or "")
end

-- ===== feature: currency / hero (bleibt) =====
local function fn_currency(args) local key=args[2]; if isempty(key) then return "" end; return tostring(tr_currency(key) or "") end
local function fn_hero(args)
  local name=args[2]; if isempty(name) then return "" end
  local H=heroes(); local real=pick_key(H,name); if not real then return "" end
  local hero=H[real]; if not hero then return "" end
  local path,i={},3; while args[i] do table.insert(path,args[i]); i=i+1 end
  local v=deep_get(hero,path); local last=norm(path[#path] or "")
  if last=="class" then return tr("class",v) or ""
  elseif last=="attackstyle" then return tr("attackstyle",v) or ""
  elseif last=="specialization" then return tr("specialization",v) or ""
  elseif last=="resource" then return tr("resource",v) or ""
  else return tostring(v or "") end
end

-- ===== main dispatcher =====
function M.main(frame)
  local a=getArgs(frame); for i=1,10 do a[i]=a[i] and tostring(a[i]) or nil end
  local dom=a[1] and norm(a[1]) or ""
  if dom=="event"     then return fn_event(a)
  elseif dom=="currency" then return fn_currency(a)
  elseif dom=="hero"     then return fn_hero(a)
  else return "" end
end

return M