Zum Inhalt springen
Das Halloween-Event (Süßes oder Saures) findet vom 24.10.2025 bis 07.11.2025 statt.

Modul:Firestone: Unterschied zwischen den Versionen

Aus Firestone Idle RPG Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 2: Zeile 2:
local M = {}
local M = {}


-- ===== 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
   if s == nil then return "" end
   s = mw.text.trim(tostring(s))
   s = mw.text.trim(tostring(s))
   return mw.ustring.lower(s):gsub("%s+"," ")
   return mw.ustring.lower(s):gsub("%s+"," ")
end
end


-- bevorzugt Parent-Args (typisch bei Vorlagen)
local function getArgs(frame)
local function getArgs(frame)
  -- bevorzugt die Parameter der Vorlage (Elternframe)
   local p = frame:getParent()
   local a, 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 if v~=nil and v~="" then a[k]=mw.text.trim(tostring(v)) end end
  local a = {}
   for k,v in pairs(src) do 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)
   local kn = norm(k2)
   for k,_ in pairs(tbl) do if norm(k)==kn then return k end end
   for k,_ in pairs(tbl) do if norm(k) == kn then return k end end
   return nil
   return nil
end
end
Zeile 31: Zeile 32:
   local cur = tbl
   local cur = tbl
   for _,k in ipairs(path) do
   for _,k in ipairs(path) do
     if type(cur)~="table" then return nil end
     if type(cur) ~= "table" then return nil end
     local real = pick_key(cur, k); if not real then return nil end
     local real = pick_key(cur, k); if not real then return nil end
     cur = cur[real]
     cur = cur[real]
Zeile 38: Zeile 39:
end
end


local function fileWikitext(file, opts)
local function fileTag(file, opts) -- [[File:...]]
   if isempty(file) then return "" end
   if isempty(file) then return "" end
   local parts = {"File:"..file}
   local parts = { "Datei:"..file }
   if opts and opts.size then table.insert(parts, opts.size) end
   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.param then table.insert(parts, opts.param) end -- z.B. "thumb|Text"
  if opts and opts.class then table.insert(parts, "class="..opts.class) end
  if opts and opts.thumb then
    table.insert(parts, "thumb")
    if opts.caption and opts.caption~="" then table.insert(parts, opts.caption) end
  end
   return string.format("[[%s]]", table.concat(parts, "|"))
   return string.format("[[%s]]", table.concat(parts, "|"))
end
end


local function heading(level, text)
-- ========= lazy loads =========
  if isempty(text) then return "" end
local I18N, EVENTS, HEROES
  local marks = (level==3) and "===" or "=="
local function i18n()
  return string.format("%s %s %s", marks, text, marks)
   if I18N then return I18N end
end
   for _,t in ipairs{ "Modul:HeroI18n", "Module:HeroI18n" } do
 
     local ok, data = pcall(mw.loadData, t) ; if ok and type(data)=="table" then I18N=data; return I18N end
-- ===== lazy loads =====
     local ok2, mod = pcall(require, t)     ; if ok2 and type(mod) =="table" then I18N=mod ; return I18N end
local HEROES, EVENTS, I18N
 
local function heroes()
   if HEROES then return HEROES end
   for _,t in ipairs{ "Module:HeroData","Modul:HeroData" } do
     local ok, data = pcall(mw.loadData, t)
    if ok and type(data)=="table" and type(data.heroes)=="table" then HEROES=data.heroes; return HEROES end
     local ok2, mod = pcall(require, t)
    if ok2 and type(mod)=="table" and type(mod.heroes)=="table" then HEROES=mod.heroes; return HEROES end
   end
   end
   HEROES = {}; return HEROES
   I18N = { i18n={}, events={ names={}, sections={default={}}, infobox={}, links={}, phrases={}, unlocks={} } }
  return I18N
end
end
local function events()
local function events()
   if EVENTS then return EVENTS end
   if EVENTS then return EVENTS end
   for _,t in ipairs{ "Module:EventData","Modul:EventData" } do
   for _,t in ipairs{ "Modul:EventData", "Module:EventData" } do
     local ok, data = pcall(mw.loadData, t)
     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
    if ok and type(data)=="table" and type(data.events)=="table" then EVENTS=data.events; return EVENTS end
     local ok2, mod = pcall(require, t)    ; if ok2 and type(mod) =="table" and type(mod.events)=="table" then EVENTS=mod.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
   EVENTS = {}; return EVENTS
end
end
 
local function heroes()
local function i18n()
   if HEROES then return HEROES end
   if I18N then return I18N end
   for _,t in ipairs{ "Modul:HeroData", "Module:HeroData" } do
   for _,t in ipairs{ "Module:HeroI18n","Modul:HeroI18n" } do
     local ok, data = pcall(mw.loadData, t) ; if ok and type(data)=="table" and type(data.heroes)=="table" then HEROES=data.heroes; return HEROES end
     local ok, data = pcall(mw.loadData, t)
     local ok2, mod = pcall(require, t)    ; if ok2 and type(mod) =="table" and type(mod.heroes)=="table" then HEROES=mod.heroes ; return HEROES end
    if ok and type(data)=="table" then I18N=data; 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={},links={},units={}} }
   HEROES = {}; return HEROES
  return I18N
end
end


-- ===== translations/formatters =====
-- ========= i18n helpers =========
local function tr(kind, val)
local function tr_simple(kind, key)
   if isempty(val) then return val end
   if isempty(key) then return "" end
   local map = (i18n().i18n or {})[kind] or {}
   local map = (i18n().i18n or {})[kind] or {}
   return map[norm(val)] or val
   return map[norm(key)] or key
end
end


local function tr_month(val)
local function tr_month(m) return tr_simple("month", m) end
  if isempty(val) then return val end
local function tr_duration(d) return tr_simple("duration", d) end
   return tr("month", val)
 
local function ev_label(evkey, which)
  local E = i18n().events or {}
  local spec = (E.sections and E.sections[norm(evkey)]) or {}
  local def  = (E.sections and E.sections.default) or {}
   return (spec[which] or def[which] or "")
end
end


local function tr_event_name(evkey)
local function ev_name(evkey)
   local names = (i18n().events or {}).names or {}
   local names = (i18n().events or {}).names or {}
   return names[norm(evkey)] or evkey
   return names[norm(evkey)] or evkey
end
end


local function section_label(evkey, which)
-- ========= event core =========
   local E = i18n().events or {}
local function get_event(evkey)
   local ev = (E.sections and E.sections[norm(evkey)]) or {}
   local evs = events()
   local def= (E.sections and E.sections.default) or {}
   local real = pick_key(evs, evkey)
  return (ev[which] or def[which] or "")
   return real and evs[real] or nil, real or evkey
end
end


local function type_display(t)
-- plain field with built-in translations for exactly the keys,
   local map = ((i18n().events or {}).infobox or {}).type or {}
-- die du in der Vorlage verwendest
  return map[norm(t or "")] or (t or "")
local function event_field(ev, key, evkey)
end
   if key == "banner" then return ev.banner or ""
 
  elseif key == "type" then
local function type_link(t)
    local t = ev.type or ""
   local L = ((i18n().events or {}).links or {}).type or {}
    local map = ((i18n().events or {}).infobox or {}).type or {}
   local target = L[norm(t or "")] or ""
    return map[norm(t)] or t
  local disp  = type_display(t)
  elseif key == "month" then
   if target~="" then
    return tr_month(ev.month or ev.date or "")
     return string.format("[[%s|%s]]", target, disp)
  elseif key == "duration" then
    return tr_duration(ev.duration or "")
   elseif key == "character_level_10" then
    return ((i18n().events or {}).unlocks or {}).character_level_10 or "Level 10"
   elseif key == "currency" then
    return ev_label(evkey, "currency")
  elseif key == "currencies" then
    return ev_label(evkey, "currencies")
   elseif key == "ex_shop" or key=="shopname" or key=="fullname" or key=="shortname"
    or key=="currency_exchange" or key=="avatar_exchange" or key=="name" or key=="eventname" then
    local k = key == "eventname" and "name" or key
     return ev_label(evkey, k)
   else
   else
     return disp
    -- generischer Pfad (falls du mal Werte direkt aus EventData brauchst)
     return tostring(ev[key] or "")
   end
   end
end
end


local function fmt_duration(s)
-- ==/=== Überschriften ==
   if isempty(s) then return s end
local function htag(level, text)
   local U = (i18n().events or {}).units or {}
   if isempty(text) then return "" end
  local singular = U.week_singular or "Woche"
   local eq = level==3 and "===" or "=="
  local plural  = U.week_plural  or "Wochen"
   return string.format("%s %s %s", eq, text, eq)
  -- "2 weeks" / "1 week"
  local n = s:match("^(%d+)%s+weeks?$")
   if n then
    local num = tonumber(n) or 0
    return string.format("%d %s", num, (num==1 and singular or plural))
  end
  return s
end
end


local function fmt_unlocks(v)
-- [[File:...|thumb|...]]
   -- "character_level_10" -> "Level 10"
local function event_file(ev, evkey, which)
  if isempty(v) then return v end
   if which=="deco" then
  local n = tostring(v):match("^character_level_(%d+)$")
    local file  = ev.images and ev.images.deco or ""
   if n then
    local legend = ev_label(evkey, "deco")
     local fmt = (((i18n().events or {}).infobox or {}).unlocks_format) or "Level $1"
    return fileTag(file, { param = "thumb|"..legend })
     return (fmt:gsub("%$1", n))
   elseif which=="fullname" then
     local file  = ev.images and ev.images.fullname or ""
    local legend = ev_label(evkey, "fullname")
    return fileTag(file, { param = "thumb|"..legend })
  elseif which=="banner" then
     return fileTag(ev.banner, { param="thumb" })
   end
   end
   return v
   return ""
end
end


-- ===== event helpers =====
-- Links, z.B. [[Calendar Events|Kalenderevent]]
local function resolve_event(key)
local function event_link(ev, evkey, which)
   local evs = events()
   if which=="type" then
  local real = pick_key(evs, key)
    local page = ((i18n().events or {}).links or {}).type or "Calendar Events"
  return real and evs[real] or nil
    local label= event_field(ev, "type", evkey)
    return string.format("[[%s|%s]]", page, label)
  end
  return ""
end
end


local function render_history(ev)
-- History-List bullets
   local H = ev.history or {}
local function event_history(ev)
  if #H==0 then return "" end
   local H = ev.history or {}; if #H==0 then return "" end
   local lines = {}
   local alt_gp = ((i18n().events or {}).phrases or {}).alt_platform or "auf Google Play"
  local t = {}
   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 line = string.format("* %s: %s bis %s", tostring(h.year or ""), tostring(h.start or ""), tostring(h["end"] or ""))
     if h.alt_platform and h.alt_platform~="" then
     if h.alt_platform then
       base = base .. string.format(" (%s auf Google Play)", tostring(h.alt_platform))
       line = line .. string.format(" (%s %s)", tostring(h.alt_platform), alt_gp)
     end
     end
     table.insert(lines, base)
     table.insert(t, line)
   end
   end
   return table.concat(lines, "\n")
   return table.concat(t, "\n")
end
end


local function render_avatars_all(ev, evkey)
-- Avatars: alle Jahre in einem Rutsch
local function event_avatars(ev, evkey)
   local AV = ev.avatars or {}
   local AV = ev.avatars or {}
   -- Jahre sortieren
   local token_icon = ev.currency and ev.currency.icon or ""
  local token_name = event_field(ev, "currencies", evkey)
  local out = {}
 
   local years = {}
   local years = {}
   for y,_ in pairs(AV) do table.insert(years, y) end
   for y,_ in pairs(AV) do table.insert(years, y) end
   table.sort(years)
   table.sort(years)
  local out = {}
  local token_key = ev.token and ev.token.key or ""
  local token_icon= ev.token and ev.token.icon or ""
  local tokens_lbl= section_label(evkey, "tokens")


   for _,year in ipairs(years) do
   for _,year in ipairs(years) do
    local list = AV[year]
     table.insert(out, string.format("==== %s ====", year))
     table.insert(out, string.format("==== %d ====", year))
     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')
     for _,it in ipairs(AV[year]) do
     for _,it in ipairs(list) do
       table.insert(out, "|-")
       table.insert(out, "|-")
       table.insert(out,
       table.insert(out, string.format("|%s || %s || %s %s %s",
        string.format("|[[File:%s|50px]] || %s || [[File:%s|25px]] %s %s",
        fileTag(it.file, { size="50px" }),
          tostring(it.file or ""),
        it.title or "",
          tostring(it.title or ""),
        fileTag(token_icon, { size="25px" }),
          tostring(token_icon or ""),
        tostring(it.cost or 0),
          tostring(it.cost or 0),
         token_name
          tokens_lbl
       ))
         )
       )
     end
     end
     table.insert(out, "|}")
     table.insert(out, "|}")
    table.insert(out, "") -- Leerzeile
   end
   end
   return table.concat(out, "\n")
   return table.concat(out, "\n")
end
end


local function render_exchange(ev, evkey)
-- Exchange-Tabelle (kurz & robust)
   local tok_icon = ev.token and ev.token.icon or ""
local function event_exchange(ev, evkey)
   local tokens  = section_label(evkey, "tokens")
   local ex = ev.exchange or {}
  local token = ev.currency or {}
  local token_icon = token.icon or ""
   local token_name = event_field(ev, "currencies", evkey)


   local rows = {}
   local function table_for(list, header)
  local function push_header()
    if type(list) ~= "table" or #list==0 then return "" end
     table.insert(rows, '{| class="article-table" style="font-size: 14px;"')
    local t = {}
     table.insert(rows, "!Angebot\n!Preis\n!Limit")
     table.insert(t, '{| class="article-table" style="font-size:14px;"')
     table.insert(t, "! "..header.." !! Preis !! Limit")
    for _,it in ipairs(list) do
      table.insert(t, "|-")
      local offer = string.format("[[Datei:%s|30px]] %s", it.icon or "", it.title or "")
      if it.range then offer = "level "..it.range..":  "..offer end
      table.insert(t, string.format("| %s || %s %s %s || %s",
        offer,
        fileTag(token_icon, { size="25px" }),
        tostring(it.price or 0),
        token_name,
        tostring(it.limit or "")
      ))
    end
    table.insert(t, "|}")
    return table.concat(t, "\n")
   end
   end


   local function push_row_offer(range, icon, title, price, limit)
   local parts = {}
    table.insert(rows, "|-")
  if ex.chests_by_level then table.insert(parts, table_for(ex.chests_by_level, "Angebot")) end
    table.insert(rows, string.format("|%s: [[File:%s|30px]] 1 [[%s|%s]] || [[File:%s|25px]] %s %s || %s",
   if ex.chests_by_stars then table.insert(parts, table_for(ex.chests_by_stars, "Angebot")) end
      range, icon, title, title, tok_icon, tostring(price or 0), tokens, tostring(limit or "")
   if ex.chests_by_oracle then table.insert(parts, table_for(ex.chests_by_oracle, "Angebot")) end
    ))
   if ex.currencies then table.insert(parts, table_for(ex.currencies, "Angebot")) end
  end
   return table.concat(parts, "\n")
 
   local function push_row_currency(icon, amount, title, price, limit)
    table.insert(rows, "|-")
    table.insert(rows, string.format("|[[File:%s|30px]] %s [[%s|%s]] || [[File:%s|25px]] %s %s || %s",
      icon, tostring(amount or 0), title, title, tok_icon, tostring(price or 0), tokens, tostring(limit or "")
    ))
  end
 
   push_header()
  for _,it in ipairs(ev.exchange.chests_by_level or {}) do
    push_row_offer("Stufe "..it.range, it.icon, it.title, it.price, it.limit)
  end
  for _,it in ipairs(ev.exchange.chests_by_stars or {}) do
    push_row_offer("Sterne "..it.range, it.icon, it.title, it.price, it.limit)
  end
  for _,it in ipairs(ev.exchange.chests_by_oracle or {}) do
    push_row_offer("Orakel "..it.range, it.icon, it.title, it.price, it.limit)
  end
   for _,it in ipairs(ev.exchange.currencies or {}) do
    push_row_currency(it.icon, it.amount, it.title, it.price, it.limit)
  end
  table.insert(rows, "|}")
  -- Fußnote (optional)
  local gp_req = deep_get(ev, {"exchange","notes","gp_req"})
  if gp_req and gp_req~="" then
    table.insert(rows, "*erfordert außerdem mehr als "..gp_req.." gesamte [[Gear Power|Ausrüstungsstärke]]")
  end
   return table.concat(rows, "\n")
end
end


local function render_offers(ev, kind, evkey)
-- Shop-Angebote
   local list = (kind=="start") and ((ev.shop or {}).initial) or ((ev.shop or {}).after)
local function event_offers(ev, evkey, which)
   if type(list)~="table" or #list==0 then return "" end
   local list = (ev.shop or {})[which]
   local tokens = section_label(evkey, "tokens")
   if type(list) ~= "table" or #list==0 then return "" end
   local token_icon = ev.currency and ev.currency.icon or ""
  local token_name = event_field(ev, "currencies", evkey)


   if kind=="start" then
   local t = {}
    -- 3 Spalten wie im Original
   table.insert(t, '{| class="article-table" style="font-size:14px;"')
    local out = {
  table.insert(t, "! colspan=\"2\" style=\"text-align:center\" | ")
      '{| class="article-table" style="font-size: 14px;"',
  table.insert(t, "|-")
      '! colspan="2" style="text-align:center" | '..(list[1].name or "")..'<br />[[File:'..(list[1].image or "")..'|x50px]]',
  for _,it in ipairs(list) do
      '! colspan="2" style="text-align:center" | '..(list[2].name or "")..'<br />[[File:'..(list[2].image or "")..'|x50px]]',
    table.insert(t, string.format("| %s || %s %s %s",
      '! colspan="2" style="text-align:center" | '..(list[3].name or "")..'<br />[[File:'..(list[3].image or "")..'|x50px]]',
      "''"..(it.name or "").."''<br />"..fileTag(it.image, { size="x50px" }),
      '|-',
      fileTag(token_icon, { size="30px" }),
      '[[File:CurrencyPumpkin.png|30px]] || '..tostring(list[1].token or 0).." "..tokens,
      tostring(it.token or 0),
      '[[File:CurrencyPumpkin.png|30px]] || '..tostring(list[2].token or 0).." "..tokens,
      token_name
      '[[File:CurrencyPumpkin.png|30px]] || '..tostring(list[3].token or 0).." "..tokens,
    ))
      '|-',
    table.insert(t, "|-")
      'Preis: || *',
      'Preis: || *',
      'Preis: || *',
      '|}',
      '&nbsp;* der Preis hängt von deiner Plattform ab',
    }
    return table.concat(out, "\n")
   else
    -- 4 Spalten wie im Original
    local headers = {}
    table.insert(headers, '{| class="article-table" style="font-size: 14px;"')
    table.insert(headers, "|-")
    for i=1,#list do
      local it=list[i]
      table.insert(headers, string.format('! colspan="2" style="text-align:center" | %s<br />[[File:%s|x50px]]', it.name or "", it.image or ""))
    end
    table.insert(headers, "|-")
    local prices = {}
    for i=1,#list do
      local it=list[i]
      table.insert(prices, string.format('[[File:CurrencyPumpkin.png|30px]] || %s %s', tostring(it.token or 0), tokens))
    end
    local foot = {}
    for i=1,#list do table.insert(foot, "Preis: || *") end
    table.insert(headers, table.concat(prices, "\n"))
    table.insert(headers, "|-")
    table.insert(headers, table.concat(foot, "\n"))
    table.insert(headers, "|}")
    table.insert(headers, "&nbsp;* der Preis hängt von deiner Plattform ab")
    return table.concat(headers, "\n")
  end
end
 
local function render_file(evkey, which, ev)
  ev = ev or {}
  local imgs = ev.images or {}
  local it = imgs[which] or {}
  local caption
  if which=="deco" then caption = section_label(evkey, "deco_caption")
  elseif which=="ex_shop" then caption = section_label(evkey, "hub")
  elseif which=="fullname" then caption = section_label(evkey, "fullname")
   end
   end
   return fileWikitext(it.file, {thumb=true, caption=caption})
  table.insert(t, "|}")
   return table.concat(t, "\n")
end
end


-- ====== EVENT dispatcher ======
-- ===== main dispatcher for Event =====
local function fn_event(args)
local function dispatch_event(args)
   local key = args[2]; if isempty(key) then return "" end
   local evkey = args[2]; if isempty(evkey) then return "" end
   local ev = resolve_event(key); if not ev then return "" end
   local ev, realkey = get_event(evkey); if not ev then return "" end
  local evkey = pick_key(events(), key) or key


   local a3, a4 = args[3], args[4]
   local a3, a4 = args[3], args[4]
 
   if isempty(a3) or a3=="eventname" then
   if isempty(a3) then return "" end
     return ev_name(realkey)
 
  -- Überschriften
  if a3=="h2" then
    return heading(2, section_label(evkey, a4 or ""))
  elseif a3=="h3" then
     return heading(3, section_label(evkey, a4 or ""))
   end
   end


   -- Bilder
   -- plain fields & labels
   if a3=="file" then
   if a3=="banner" or a3=="type" or a3=="month" or a3=="duration"
     return render_file(evkey, a4 or "", ev)
    or a3=="character_level_10"
    or a3=="currency" or a3=="currencies"
    or a3=="ex_shop" or a3=="fullname" or a3=="shortname"
    or a3=="shopname" or a3=="currency_exchange" or a3=="avatar_exchange"
    or a3=="name" then
     return event_field(ev, a3, realkey)
   end
   end


   -- Links
   -- file / link / headings
   if a3=="link" and a4=="type" then
   if a3=="file" then return event_file(ev, realkey, a4 or "")
    return type_link(ev.type)
  elseif a3=="link" then return event_link(ev, realkey, a4 or "")
   end
  elseif a3=="h2" then return htag(2, ev_label(realkey, a4 or ""))
   elseif a3=="h3"  then return htag(3, ev_label(realkey, a4 or "")) end


   -- zusammengesetzte Renderings
   -- composed blocks
   if a3=="history" then
   if a3=="history" then return event_history(ev)
    return render_history(ev)
   elseif a3=="avatars" then return event_avatars(ev, realkey)
   elseif a3=="avatars" then
   elseif a3=="exchange" then return event_exchange(ev, realkey)
    return render_avatars_all(ev, evkey)
   elseif a3=="offers" and (a4=="start" or a4=="more") then
   elseif a3=="exchange" then
     return event_offers(ev, realkey, (a4=="start") and "initial" or "after")
    return render_exchange(ev, evkey)
   elseif a3=="offers" then
    local kind = args[4] -- "start" | "more"
     return render_offers(ev, kind, evkey)
   end
   end


   -- einfache Felder/Labels
   return ""
  if a3=="banner" then
end
    return tostring(ev.banner or "")
  elseif a3=="type" then
    return type_display(ev.type)
  elseif a3=="month" or a3=="date" then
    return tr_month(ev.month or ev.date or "")
  elseif a3=="duration" then
    return fmt_duration(ev.duration)
  elseif a3=="unlocks_at" then
    return fmt_unlocks(ev.unlocks_at)
  elseif a3=="eventname" then
    return tr_event_name(evkey)
  end
 
  -- Freiform-Schlüssel wie "character_level_10"
  do
    local maybe = fmt_unlocks(a3)
    if maybe ~= a3 then return maybe end
  end
 
  -- reine Label-Getter aus Sections (currencies, shopname, ex_shop, fullname, shortname, name …)
  local lbl = section_label(evkey, a3)
  if not isempty(lbl) then return lbl end


  -- Fallback auf tiefe Pfade: {{Firestone|Event|name|path|to|field}}
-- ===== hero (nur für Vollständigkeit deiner Wünsche) =====
local function dispatch_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 h = H[real]
   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(ev, path)
   local v = deep_get(h, path)
   return tostring(v or "")
   return tostring(v or "")
end
end
Zeile 398: Zeile 322:
function M.main(frame)
function M.main(frame)
   local a = getArgs(frame)
   local a = getArgs(frame)
  -- numerische Parameter durchreichen
   for i=1,10 do a[i] = a[i] and tostring(a[i]) or nil end
   for i=1,12 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 dispatch_event(a)
   if dom=="event" then
  elseif dom=="hero" then return dispatch_hero(a)
    return fn_event(a)
   else return "" end
   else
    return ""
  end
end
end


return M
return M

Version vom 20. Oktober 2025, 18:55 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

-- bevorzugt Parent-Args (typisch bei Vorlagen)
local function getArgs(frame)
  local p = frame:getParent()
  local src = (p and p.args) or frame.args or {}
  local a = {}
  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 fileTag(file, opts) -- [[File:...]]
  if isempty(file) then return "" end
  local parts = { "Datei:"..file }
  if opts and opts.size   then table.insert(parts, opts.size) end
  if opts and opts.param  then table.insert(parts, opts.param) end -- z.B. "thumb|Text"
  return string.format("[[%s]]", table.concat(parts, "|"))
end

-- ========= lazy loads =========
local I18N, EVENTS, HEROES
local function i18n()
  if I18N then return I18N end
  for _,t in ipairs{ "Modul:HeroI18n", "Module:HeroI18n" } do
    local ok, data = pcall(mw.loadData, t) ; if ok and type(data)=="table" then I18N=data; return I18N end
    local ok2, mod = pcall(require, t)      ; if ok2 and type(mod) =="table" then I18N=mod ; return I18N end
  end
  I18N = { i18n={}, events={ names={}, sections={default={}}, infobox={}, links={}, phrases={}, unlocks={} } }
  return I18N
end
local function events()
  if EVENTS then return EVENTS end
  for _,t in ipairs{ "Modul:EventData", "Module:EventData" } do
    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
    local ok2, mod = pcall(require, t)     ; if ok2 and type(mod) =="table" and type(mod.events)=="table" then EVENTS=mod.events ; return EVENTS end
  end
  EVENTS = {}; return EVENTS
end
local function heroes()
  if HEROES then return HEROES end
  for _,t in ipairs{ "Modul:HeroData", "Module:HeroData" } do
    local ok, data = pcall(mw.loadData, t) ; if ok and type(data)=="table" and type(data.heroes)=="table" then HEROES=data.heroes; return HEROES end
    local ok2, mod = pcall(require, t)     ; if ok2 and type(mod) =="table" and type(mod.heroes)=="table" then HEROES=mod.heroes ; return HEROES end
  end
  HEROES = {}; return HEROES
end

-- ========= i18n helpers =========
local function tr_simple(kind, key)
  if isempty(key) then return "" end
  local map = (i18n().i18n or {})[kind] or {}
  return map[norm(key)] or key
end

local function tr_month(m) return tr_simple("month", m) end
local function tr_duration(d) return tr_simple("duration", d) end

local function ev_label(evkey, which)
  local E = i18n().events or {}
  local spec = (E.sections and E.sections[norm(evkey)]) or {}
  local def  = (E.sections and E.sections.default) or {}
  return (spec[which] or def[which] or "")
end

local function ev_name(evkey)
  local names = (i18n().events or {}).names or {}
  return names[norm(evkey)] or evkey
end

-- ========= event core =========
local function get_event(evkey)
  local evs = events()
  local real = pick_key(evs, evkey)
  return real and evs[real] or nil, real or evkey
end

-- plain field with built-in translations for exactly the keys,
-- die du in der Vorlage verwendest
local function event_field(ev, key, evkey)
  if key == "banner" then return ev.banner or ""
  elseif key == "type" then
    local t = ev.type or ""
    local map = ((i18n().events or {}).infobox or {}).type or {}
    return map[norm(t)] or t
  elseif key == "month" then
    return tr_month(ev.month or ev.date or "")
  elseif key == "duration" then
    return tr_duration(ev.duration or "")
  elseif key == "character_level_10" then
    return ((i18n().events or {}).unlocks or {}).character_level_10 or "Level 10"
  elseif key == "currency" then
    return ev_label(evkey, "currency")
  elseif key == "currencies" then
    return ev_label(evkey, "currencies")
  elseif key == "ex_shop" or key=="shopname" or key=="fullname" or key=="shortname"
     or key=="currency_exchange" or key=="avatar_exchange" or key=="name" or key=="eventname" then
    local k = key == "eventname" and "name" or key
    return ev_label(evkey, k)
  else
    -- generischer Pfad (falls du mal Werte direkt aus EventData brauchst)
    return tostring(ev[key] or "")
  end
end

-- ==/=== Überschriften ==
local function htag(level, text)
  if isempty(text) then return "" end
  local eq = level==3 and "===" or "=="
  return string.format("%s %s %s", eq, text, eq)
end

-- [[File:...|thumb|...]]
local function event_file(ev, evkey, which)
  if which=="deco" then
    local file   = ev.images and ev.images.deco or ""
    local legend = ev_label(evkey, "deco")
    return fileTag(file, { param = "thumb|"..legend })
  elseif which=="fullname" then
    local file   = ev.images and ev.images.fullname or ""
    local legend = ev_label(evkey, "fullname")
    return fileTag(file, { param = "thumb|"..legend })
  elseif which=="banner" then
    return fileTag(ev.banner, { param="thumb" })
  end
  return ""
end

-- Links, z.B. [[Calendar Events|Kalenderevent]]
local function event_link(ev, evkey, which)
  if which=="type" then
    local page = ((i18n().events or {}).links or {}).type or "Calendar Events"
    local label= event_field(ev, "type", evkey)
    return string.format("[[%s|%s]]", page, label)
  end
  return ""
end

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

-- Avatars: alle Jahre in einem Rutsch
local function event_avatars(ev, evkey)
  local AV = ev.avatars or {}
  local token_icon = ev.currency and ev.currency.icon or ""
  local token_name = event_field(ev, "currencies", evkey)
  local out = {}

  local years = {}
  for y,_ in pairs(AV) do table.insert(years, y) end
  table.sort(years)

  for _,year in ipairs(years) do
    table.insert(out, string.format("==== %s ====", year))
    table.insert(out, '{| class="article-table" style="font-size:14px;"')
    table.insert(out, '! colspan="2" | Avatar\n! Preis')
    for _,it in ipairs(AV[year]) do
      table.insert(out, "|-")
      table.insert(out, string.format("|%s || %s || %s %s %s",
        fileTag(it.file, { size="50px" }),
        it.title or "",
        fileTag(token_icon, { size="25px" }),
        tostring(it.cost or 0),
        token_name
      ))
    end
    table.insert(out, "|}")
  end
  return table.concat(out, "\n")
end

-- Exchange-Tabelle (kurz & robust)
local function event_exchange(ev, evkey)
  local ex = ev.exchange or {}
  local token = ev.currency or {}
  local token_icon = token.icon or ""
  local token_name = event_field(ev, "currencies", evkey)

  local function table_for(list, header)
    if type(list) ~= "table" or #list==0 then return "" end
    local t = {}
    table.insert(t, '{| class="article-table" style="font-size:14px;"')
    table.insert(t, "! "..header.." !! Preis !! Limit")
    for _,it in ipairs(list) do
      table.insert(t, "|-")
      local offer = string.format("[[Datei:%s|30px]] %s", it.icon or "", it.title or "")
      if it.range then offer = "level "..it.range..":&nbsp;&nbsp;"..offer end
      table.insert(t, string.format("| %s || %s %s %s || %s",
        offer,
        fileTag(token_icon, { size="25px" }),
        tostring(it.price or 0),
        token_name,
        tostring(it.limit or "")
      ))
    end
    table.insert(t, "|}")
    return table.concat(t, "\n")
  end

  local parts = {}
  if ex.chests_by_level then table.insert(parts, table_for(ex.chests_by_level, "Angebot")) end
  if ex.chests_by_stars then table.insert(parts, table_for(ex.chests_by_stars, "Angebot")) end
  if ex.chests_by_oracle then table.insert(parts, table_for(ex.chests_by_oracle, "Angebot")) end
  if ex.currencies then table.insert(parts, table_for(ex.currencies, "Angebot")) end
  return table.concat(parts, "\n")
end

-- Shop-Angebote
local function event_offers(ev, evkey, which)
  local list = (ev.shop or {})[which]
  if type(list) ~= "table" or #list==0 then return "" end
  local token_icon = ev.currency and ev.currency.icon or ""
  local token_name = event_field(ev, "currencies", evkey)

  local t = {}
  table.insert(t, '{| class="article-table" style="font-size:14px;"')
  table.insert(t, "! colspan=\"2\" style=\"text-align:center\" | ")
  table.insert(t, "|-")
  for _,it in ipairs(list) do
    table.insert(t, string.format("| %s || %s %s %s",
      "''"..(it.name or "").."''<br />"..fileTag(it.image, { size="x50px" }),
      fileTag(token_icon, { size="30px" }),
      tostring(it.token or 0),
      token_name
    ))
    table.insert(t, "|-")
  end
  table.insert(t, "|}")
  return table.concat(t, "\n")
end

-- ===== main dispatcher for Event =====
local function dispatch_event(args)
  local evkey = args[2]; if isempty(evkey) then return "" end
  local ev, realkey = get_event(evkey); if not ev then return "" end

  local a3, a4 = args[3], args[4]
  if isempty(a3) or a3=="eventname" then
    return ev_name(realkey)
  end

  -- plain fields & labels
  if a3=="banner" or a3=="type" or a3=="month" or a3=="duration"
     or a3=="character_level_10"
     or a3=="currency" or a3=="currencies"
     or a3=="ex_shop" or a3=="fullname" or a3=="shortname"
     or a3=="shopname" or a3=="currency_exchange" or a3=="avatar_exchange"
     or a3=="name" then
    return event_field(ev, a3, realkey)
  end

  -- file / link / headings
  if a3=="file" then return event_file(ev, realkey, a4 or "")
  elseif a3=="link" then return event_link(ev, realkey, a4 or "")
  elseif a3=="h2"  then return htag(2, ev_label(realkey, a4 or ""))
  elseif a3=="h3"  then return htag(3, ev_label(realkey, a4 or "")) end

  -- composed blocks
  if a3=="history" then return event_history(ev)
  elseif a3=="avatars" then return event_avatars(ev, realkey)
  elseif a3=="exchange" then return event_exchange(ev, realkey)
  elseif a3=="offers" and (a4=="start" or a4=="more") then
    return event_offers(ev, realkey, (a4=="start") and "initial" or "after")
  end

  return ""
end

-- ===== hero (nur für Vollständigkeit deiner Wünsche) =====
local function dispatch_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 h = H[real]
  local path, i = {}, 3
  while args[i] do table.insert(path, args[i]); i=i+1 end
  local v = deep_get(h, path)
  return tostring(v or "")
end

-- ===== main =====
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 dispatch_event(a)
  elseif dom=="hero" then return dispatch_hero(a)
  else return "" end
end

return M