Modul:Firestone: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung  | 
				Keine Bearbeitungszusammenfassung  | 
				||
| Zeile 199: | Zeile 199: | ||
end  | end  | ||
local function tabber_render(pairs)  | |||
local function   |   -- pairs = { {label=..., content=...}, ... }  | ||
  if mw.ext then  | |||
   local   |     if mw.ext.tabber and mw.ext.tabber.render then  | ||
      return mw.ext.tabber.render(pairs)  | |||
    end  | |||
    if mw.ext.TabberNeue and mw.ext.TabberNeue.render then  | |||
      return mw.ext.TabberNeue.render(pairs)  | |||
    end  | |||
   end  | |||
  -- Fallback per Parserfunktion; 1= verhindert Pipe-Zerlegung  | |||
  local parts = {}  | |||
  for _,t in ipairs(pairs) do  | |||
    parts[#parts+1] = (t.label or "") .. "=\n" .. (t.content or "")  | |||
  end  | |||
  return "{{#tag:tabber|1=\n" .. table.concat(parts, "\n|-|\n") .. "\n}}"  | |||
end  | |||
-- Baut nur den Text in der linken Zelle für Tabs (KEINE Tabelle im Tab!)  | |||
local function tabber_inline(list, group_key)  | |||
   local tabs = {}  | |||
   for _,it in ipairs(list) do  |    for _,it in ipairs(list) do  | ||
     local   |      local label   = pretty_range(it.range or "")  | ||
    local content = string.format(  | |||
      "%s %s: %s %s",  | |||
       range_prefix(group_key), pretty_range(it.range or ""),  |        range_prefix(group_key), pretty_range(it.range or ""),  | ||
       fileTag(it.icon or "", { size="30px" }),  |        fileTag(it.icon or "", { size="30px" }),  | ||
       (it.item and item_label(it.item, it.amount or 1)) or (it.title or "")  |        (it.item and item_label(it.item, it.amount or 1)) or (it.title or "")  | ||
     )  |      )  | ||
     table.  |      tabs[#tabs+1] = { label = label, content = content }  | ||
  end  | |||
  return tabber_render(tabs)  | |||
end  | |||
-- Eine Tabelle mit GENAU EINER Zeile: links Tabber, rechts Preis/Limit  | |||
local function exchange_row_table(list, group_key, token_icon, token_name)  | |||
  if type(list) ~= "table" or #list == 0 then return "" end  | |||
  local price = list[1].price or ""  | |||
  local limit = list[1].limit or ""  | |||
  local left  = tabber_inline(list, group_key)  | |||
  local t = {}  | |||
  t[#t+1] = '{| class="article-table" style="font-size:14px;"'  | |||
  t[#t+1] = "! Offer !! Price !! Limit"  | |||
  t[#t+1] = "|-"  | |||
  t[#t+1] = string.format("| %s || %s %s %s || %s",  | |||
              left,  | |||
              fileTag(token_icon, { size="25px" }),  | |||
              tostring(price), token_name,  | |||
              tostring(limit))  | |||
  t[#t+1] = "|}"  | |||
  return table.concat(t, "\n")  | |||
end  | |||
-- Separate Tabelle nur für die Währungs-Angebote (unten anhängen)  | |||
local function exchange_currency_table(cur_list, token_icon, token_name)  | |||
  if type(cur_list) ~= "table" or #cur_list == 0 then return "" end  | |||
  local rows = {}  | |||
  for _,it in ipairs(cur_list) do  | |||
    local offer = string.format("%s %s",  | |||
                   fileTag(it.icon or "", { size="30px" }),  | |||
                   item_label(it.item, it.amount or 1))  | |||
    rows[#rows+1] = string.format("|- \n| %s || %s %s %s || %s",  | |||
                      offer,  | |||
                      fileTag(token_icon, { size="25px" }),  | |||
                      tostring(it.price or 0),  | |||
                      token_name,  | |||
                      tostring(it.limit or ""))  | |||
   end  |    end  | ||
   return table.concat({  |    return table.concat({  | ||
| Zeile 221: | Zeile 272: | ||
     "! Offer !! Price !! Limit",  |      "! Offer !! Price !! Limit",  | ||
     table.concat(rows, "\n"),  |      table.concat(rows, "\n"),  | ||
     "|}"  | |||
   }, "\n")  |    }, "\n")  | ||
end  | end  | ||
--   | -- Hauptfunktion für {{Firestone|Event|NAME|exchange}}  | ||
local function event_exchange_tabs(ev, evkey)  | local function event_exchange_tabs(ev, evkey)  | ||
   local ex = ev.exchange or {}  |    local ex = ev.exchange or {}  | ||
| Zeile 246: | Zeile 284: | ||
   local blocks = {}  |    local blocks = {}  | ||
   if ex.chests_by_level and #ex.chests_by_level > 0 then  | |||
   if   |      blocks[#blocks+1] = exchange_row_table(ex.chests_by_level, "level", token_icon, token_name)  | ||
   end  |    end  | ||
   if ex.chests_by_stars and #ex.chests_by_stars > 0 then  | |||
     blocks[#blocks+1] = exchange_row_table(ex.chests_by_stars, "stars", token_icon, token_name)  | |||
   if   | |||
   end  |    end  | ||
   if ex.chests_by_oracle and #ex.chests_by_oracle > 0 then  | |||
     blocks[#blocks+1] = exchange_row_table(ex.chests_by_oracle, "oracle", token_icon, token_name)  | |||
   if   | |||
   end  |    end  | ||
   if ex.currencies and #ex.currencies > 0 then  | |||
     blocks[#blocks+1] = exchange_currency_table(ex.currencies, token_icon, token_name)  | |||
   if   | |||
     blocks[#blocks+1] =   | |||
   end  |    end  | ||
  -- Keine führenden/trailing \n → kein Extra-<p><br>  | |||
   return table.concat(blocks, "\n")  |    return table.concat(blocks, "\n")  | ||
end  | end  | ||
Version vom 21. Oktober 2025, 01:12 Uhr
Die Dokumentation für dieses Modul kann unter Modul:Firestone/Doku erstellt werden
-- Modul:Firestone (vereinfachte, generische Version)
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) -- [[Datei:...]]
  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
local function has_items(t)
  if type(t) ~= "table" then return false end
  for _ in pairs(t) do return true end
  return false
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 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
-- Auto-Übersetzung: wenn es in M.i18n einen Tisch mit dem Key-Namen gibt
-- (z. B. M.i18n.month / M.i18n.duration / M.i18n.currency …), wird
-- der String-Wert darüber übersetzt. Sonst bleibt er unverändert.
local function autotr(key_name, value)
  if type(value) ~= "string" then return value end
  local map = (i18n().i18n or {})[norm(key_name)]
  return (map and map[norm(value)]) or value
end
-- ========= Event: Renderer, die Markup erzeugen =========
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
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=="ex_shop" then
    local file   = ev.images and ev.images.ex_shop or ""
    local legend = ev_label(evkey, "ex_shop")
    return fileTag(file, { param = "thumb|"..legend })
  elseif which=="banner" then
    return fileTag(ev.banner, { param="thumb" })
  end
  return ""
end
local function event_link(ev, evkey, which)
  if which=="type" then
    local E = i18n().events or {}
    local page = (E.links and E.links.type and E.links.type[ev.type or ""]) or (E.links and E.links.type) or "Calendar Events"
    local map  = (E.infobox and E.infobox.type) or {}
    local label= map[norm(ev.type or "")] or (ev.type or "")
    return string.format("[[%s|%s]]", page, label)
  end
  return ""
end
local function event_history(ev)
  local H = ev.history or {}; if not has_items(H) 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
local function event_avatars(ev, evkey)
  local AV = ev.avatars
  if not has_items(AV) then return "" end
  local token_icon = (ev.currency or {}).icon or ""
  local token_name = ev_label(evkey, "currencies")
  local years, out = {}, {}
  for y,_ in pairs(AV) do table.insert(years, tonumber(y) or y) end
  table.sort(years, function(a,b) return tostring(a) < tostring(b) end)
  for _,year in ipairs(years) do
    local list = AV[year] or AV[tostring(year)]
    if has_items(list) then
      table.insert(out, ("==== %s ===="):format(year))
      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",
          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
  end
  return (#out > 0) and table.concat(out, "\n") or ""
end
local function pretty_range(s)
  if not s or s=="" then return "" end
  s = tostring(s)
  if s:sub(-1) == "-" then return s:sub(1, -2) .. "+" end
  return s:gsub("%-","–")
end
local function item_label(key, amount)
  local I = (i18n().items or {})[key] or {}
  local n = tonumber(amount or 1) or 1
  local txt = (n == 1) and (I.sing or key) or (I.plur or (I.sing or key))
  local disp = I.link and string.format("[[%s|%s]]", I.link, txt) or txt
  return string.format("%d %s", n, disp)
end
local function range_prefix(kind)
  local P = ((i18n().events or {}).range_prefix or {})
  return P[kind] or kind
end
local function tabber_render(pairs)
  -- pairs = { {label=..., content=...}, ... }
  if mw.ext then
    if mw.ext.tabber and mw.ext.tabber.render then
      return mw.ext.tabber.render(pairs)
    end
    if mw.ext.TabberNeue and mw.ext.TabberNeue.render then
      return mw.ext.TabberNeue.render(pairs)
    end
  end
  -- Fallback per Parserfunktion; 1= verhindert Pipe-Zerlegung
  local parts = {}
  for _,t in ipairs(pairs) do
    parts[#parts+1] = (t.label or "") .. "=\n" .. (t.content or "")
  end
  return "{{#tag:tabber|1=\n" .. table.concat(parts, "\n|-|\n") .. "\n}}"
end
-- Baut nur den Text in der linken Zelle für Tabs (KEINE Tabelle im Tab!)
local function tabber_inline(list, group_key)
  local tabs = {}
  for _,it in ipairs(list) do
    local label   = pretty_range(it.range or "")
    local content = string.format(
      "%s %s: %s %s",
      range_prefix(group_key), pretty_range(it.range or ""),
      fileTag(it.icon or "", { size="30px" }),
      (it.item and item_label(it.item, it.amount or 1)) or (it.title or "")
    )
    tabs[#tabs+1] = { label = label, content = content }
  end
  return tabber_render(tabs)
end
-- Eine Tabelle mit GENAU EINER Zeile: links Tabber, rechts Preis/Limit
local function exchange_row_table(list, group_key, token_icon, token_name)
  if type(list) ~= "table" or #list == 0 then return "" end
  local price = list[1].price or ""
  local limit = list[1].limit or ""
  local left  = tabber_inline(list, group_key)
  local t = {}
  t[#t+1] = '{| class="article-table" style="font-size:14px;"'
  t[#t+1] = "! Offer !! Price !! Limit"
  t[#t+1] = "|-"
  t[#t+1] = string.format("| %s || %s %s %s || %s",
              left,
              fileTag(token_icon, { size="25px" }),
              tostring(price), token_name,
              tostring(limit))
  t[#t+1] = "|}"
  return table.concat(t, "\n")
end
-- Separate Tabelle nur für die Währungs-Angebote (unten anhängen)
local function exchange_currency_table(cur_list, token_icon, token_name)
  if type(cur_list) ~= "table" or #cur_list == 0 then return "" end
  local rows = {}
  for _,it in ipairs(cur_list) do
    local offer = string.format("%s %s",
                   fileTag(it.icon or "", { size="30px" }),
                   item_label(it.item, it.amount or 1))
    rows[#rows+1] = string.format("|- \n| %s || %s %s %s || %s",
                      offer,
                      fileTag(token_icon, { size="25px" }),
                      tostring(it.price or 0),
                      token_name,
                      tostring(it.limit or ""))
  end
  return table.concat({
    '{| class="article-table" style="font-size:14px;"',
    "! Offer !! Price !! Limit",
    table.concat(rows, "\n"),
    "|}"
  }, "\n")
end
-- Hauptfunktion für {{Firestone|Event|NAME|exchange}}
local function event_exchange_tabs(ev, evkey)
  local ex = ev.exchange or {}
  local token_icon = (ev.currency or {}).icon or ""
  local token_name = ev_label(evkey, "currencies")
  local blocks = {}
  if ex.chests_by_level and #ex.chests_by_level > 0 then
    blocks[#blocks+1] = exchange_row_table(ex.chests_by_level, "level", token_icon, token_name)
  end
  if ex.chests_by_stars and #ex.chests_by_stars > 0 then
    blocks[#blocks+1] = exchange_row_table(ex.chests_by_stars, "stars", token_icon, token_name)
  end
  if ex.chests_by_oracle and #ex.chests_by_oracle > 0 then
    blocks[#blocks+1] = exchange_row_table(ex.chests_by_oracle, "oracle", token_icon, token_name)
  end
  if ex.currencies and #ex.currencies > 0 then
    blocks[#blocks+1] = exchange_currency_table(ex.currencies, token_icon, token_name)
  end
  -- Keine führenden/trailing \n → kein Extra-<p><br>
  return table.concat(blocks, "\n")
end
local function event_offers(ev, evkey, which)
  local list = (ev.shop or {})[which]
  if not has_items(list) then return "" end
  local token_icon = (ev.currency or {}).icon or ""
  local token_name = ev_label(evkey, "currencies")
  local t = {}
  table.insert(t, '{| class="article-table" style="font-size:14px;"')
  table.insert(t, "! Angebot !! Menge")
  for _,it in ipairs(list) do
    table.insert(t, "|-")
    table.insert(t, string.format("| ''%s''<br />%s || %s %s %s",
      it.name or "",
      fileTag(it.image, { size="x50px" }),
      fileTag(token_icon, { size="30px" }),
      tostring(it.token or 0),
      token_name
    ))
  end
  table.insert(t, "|}")
  return table.concat(t, "\n")
end
-- ========= Event-Dispatcher (generisch) =========
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
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) then return ev_name(realkey) end  -- {{Firestone|Event|NAME}}
  -- Markup-Handler
  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 ""))
  elseif a3=="history"  then return event_history(ev)
  elseif a3=="avatars"  then return event_avatars(ev, realkey)
  elseif a3=="exchange" then return event_exchange_tabs(ev, realkey)
  elseif a3=="offers"   then
    if a4=="start" then return event_offers(ev, realkey, "initial")
    elseif a4=="more" then return event_offers(ev, realkey, "after") end
    return ""
  end
  -- Generischer Pfad in EventData
  local path, i = {}, 3
  while args[i] do table.insert(path, args[i]); i=i+1 end
  local v = deep_get(ev, path)
  if v ~= nil then
    -- Auto-Übersetzung anhand des letzten Keys (falls in M.i18n vorhanden)
    local last = path[#path] and tostring(path[#path]) or ""
    v = autotr(last, v)
    return tostring(v)
  end
  -- Nicht im EventData? → Labels/Name/Unlocks aus HeroI18n
  local last = norm(path[#path] or a3)
  if last=="eventname" or last=="name" then return ev_name(realkey) end
  local lbl = ev_label(realkey, last); if not isempty(lbl) then return lbl end
  local U = ((i18n().events or {}).unlocks or {})[last]
  if U then return U end
  return ""
end
-- ========= Hero (unverändert schlicht) =========
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)
  -- Auto-Übersetzungen optional auch für Helden-Felder möglich:
  local last = path[#path] and tostring(path[#path]) or ""
  v = autotr(last, v)
  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