Modul:Firestone: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
| Zeile 261: | Zeile 261: | ||
for _,it in ipairs(list) do | for _,it in ipairs(list) do | ||
local label = pretty_range(it.range or "") | local label = pretty_range(it.range or "") | ||
local note = ref_gear_power(it.req_gear_power | local note = ref_gear_power(it.req_gear_power) | ||
local content = string.format( | local content = string.format( | ||
"%s %s", | "%s %s%s", | ||
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 ""), | ||
| Zeile 281: | Zeile 281: | ||
end | end | ||
return "<tabber>\n" .. content .. "\n</tabber>" | return "<tabber>\n" .. content .. "\n</tabber>" | ||
end | |||
local function references_tag() | |||
if FRAME and FRAME.extensionTag then | |||
return FRAME:extensionTag('references', '') | |||
end | |||
return '<references />' | |||
end | end | ||
| Zeile 415: | Zeile 422: | ||
t[#t+1] = '|}' | t[#t+1] = '|}' | ||
t[#t+1] = | t[#t+1] = references_tag() | ||
return '\n' .. table.concat(t, '\n') | return '\n' .. table.concat(t, '\n') | ||
Version vom 22. Oktober 2025, 01:43 Uhr
Die Dokumentation für dieses Modul kann unter Modul:Firestone/Doku erstellt werden
-- Modul:Firestone (generisch, mit robustem Tabber-Render)
local M = {}
-- Feste Breiten für die Austausch-Tabelle
local COL_OFFER, COL_PRICE, COL_LIMIT = "50%", "25%", "25%"
-- ========= 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
-- Tabellenkopf rein aus i18n (Fallback passiert in ev_label → sections.default)
local function header_labels(evkey)
return ev_label(evkey, "offer"), ev_label(evkey, "price"), ev_label(evkey, "limit")
end
-- Auto-Übersetzung anhand M.i18n.<key>
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 =========
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
-- ========= Big-Number + Ref (reine i18n, keine Hardcodes) =========
local function parse_sci(v) -- "5e9", "60e9", "10e6" oder number -> number
if type(v) == "number" then return v end
if type(v) == "string" then
local a,e = v:match("^%s*([%d%.]+)[eE]([%+%-]?%d+)%s*$")
if a and e then return (tonumber(a) or 0) * 10^(tonumber(e) or 0) end
local n = tonumber(v); if n then return n end
end
return nil
end
-- Einheiten vollständig aus i18n().numbers:
-- numbers.thousand_sing / numbers.thousand_plur / million_* / billion_* / trillion_*
local function i18n_big_units()
local N = (i18n().numbers or {})
return {
{1e12, N.trillion_sing, N.trillion_plur}, -- Billion / Billionen
{1e9, N.billion_sing, N.billion_plur}, -- Milliarde / Milliarden
{1e6, N.million_sing, N.million_plur}, -- Million / Millionen
{1e3, N.thousand_sing, N.thousand_plur}, -- Tausend / Tausend
}
end
local function format_big(v) -- -> "5 Milliarden", "10 Millionen", …
local n = parse_sci(v); if not n then return tostring(v or "") end
for _,u in ipairs(i18n_big_units()) do
local base, sing, plur = u[1], u[2], u[3]
if base and sing and plur and n >= base and math.floor(n % base) == 0 then
local q = math.floor(n / base)
return string.format("%d %s", q, (q==1) and sing or plur)
end
end
return tostring(n)
end
-- <ref> aus i18n.events.phrases.req_gear_power (kein Text-Fallback!)
local function ref_gear_power(v)
if isempty(v) then return "" end
local phr = (((i18n().events or {}).phrases) or {}).req_gear_power
if isempty(phr) then return "" end -- kein Fallback-Text aus Code
return string.format("<ref>%s</ref>", (phr:gsub("$1", format_big(v))))
end
-- Anzeige-Label für die Gruppen (aus i18n, sonst Fallback)
local function group_label(kind)
local G = ((i18n().events or {}).group_labels or {})
return G[kind] or range_prefix(kind)
end
-- ========= TABBER sicher rendern =========
local FRAME = nil -- wird in main gesetzt
local function build_tabber_content_for(list, group_key)
local out = {}
for _,it in ipairs(list) do
local label = pretty_range(it.range or "")
local note = ref_gear_power(it.req_gear_power)
local content = string.format(
"%s %s%s",
fileTag(it.icon or "", { size="30px" }),
(it.item and item_label(it.item, it.amount or 1)) or (it.title or ""),
note
)
out[#out+1] = "|-|" .. label .. "=\n" .. content
end
return table.concat(out, "\n")
end
-- erzeugt den Tabber sicher (erst extensionTag, dann Fallback)
local function tabber_render_from_content(content)
if FRAME and FRAME.extensionTag then
return FRAME:extensionTag('tabber', content)
end
return "<tabber>\n" .. content .. "\n</tabber>"
end
local function references_tag()
if FRAME and FRAME.extensionTag then
return FRAME:extensionTag('references', '')
end
return '<references />'
end
local function exchange_table_with_tabber(list, group_key, token_icon, token_name, currencies_or_nil)
if not has_items(list) then return "" end -- << kein #list !
-- linker Zelleninhalt (Tabber)
local parts = {}
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 "")
)
parts[#parts+1] = "|-|" .. label .. "=\n" .. content
end
local tabber = tabber_render_from_content(table.concat(parts, "\n"))
-- Preis/Limit (identisch in der Gruppe)
local price = list[1].price or 0
local limit = list[1].limit or ""
local t = {}
t[#t+1] = '{| class="article-table" style="font-size:14px;"'
t[#t+1] = '!Offer !! Price !! Limit'
t[#t+1] = '|-'
t[#t+1] = '|' .. tabber .. string.format(' || %s %s %s || %s',
fileTag(token_icon, { size="25px" }),
tostring(price), token_name,
tostring(limit))
-- optionale Currency-Zeilen
if has_items(currencies_or_nil) then
for _,it in ipairs(currencies_or_nil) do
local offer = string.format('%s %s',
fileTag(it.icon or "", { size="30px" }),
item_label(it.item, it.amount or 1)
)
t[#t+1] = '|-'
t[#t+1] = 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
end
t[#t+1] = '|}'
-- führender \n sorgt dafür, dass die Tabelle sicher startet
return "\n" .. table.concat(t, "\n")
end
-- {{Firestone|Event|NAME|exchange}} – eine Tabelle mit verschachtelten Tabbern links
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")
if not (has_items(ex.chests_by_level)
or has_items(ex.chests_by_stars)
or has_items(ex.chests_by_oracle)
or has_items(ex.currencies)) then
return ""
end
-- INNER: je Gruppe ein Tabber mit den Ranges
local outer_parts = {}
local function add_group(kind, list)
if not has_items(list) then return end
-- innerer Tabber (Ranges)
local inner_content = build_tabber_content_for(list, kind) -- "|-|1-64= …"
local inner_tabber = tabber_render_from_content(inner_content) -- bevorzugt extensionTag
-- äußerer Tabber-Eintrag: Gruppenlabel = (innerer Tabber)
outer_parts[#outer_parts+1] = "|-|" .. group_label(kind) .. "=\n" .. inner_tabber
end
add_group("level", ex.chests_by_level)
add_group("stars", ex.chests_by_stars)
add_group("oracle", ex.chests_by_oracle)
-- ÄUSSERER Tabber als rohes <tabber> (robust in Tabellenzellen)
local left_tabber = tabber_render_from_content(table.concat(outer_parts, "\n"))
-- Preis/Limit: nimm die erste vorhandene Gruppe (bei dir identisch)
local function pick_price_limit()
if has_items(ex.chests_by_level) then return ex.chests_by_level[1].price or 0, ex.chests_by_level[1].limit or "" end
if has_items(ex.chests_by_stars) then return ex.chests_by_stars[1].price or 0, ex.chests_by_stars[1].limit or "" end
if has_items(ex.chests_by_oracle) then return ex.chests_by_oracle[1].price or 0, ex.chests_by_oracle[1].limit or "" end
return 0, ""
end
local price, limit = pick_price_limit()
-- Tabelle bauen
local t = {}
local H_offer, H_price, H_limit = header_labels(evkey)
t[#t+1] = '{| class="article-table" style="font-size:14px;"'
t[#t+1] = string.format(
'! style="width:%s;" | %s !! style="width:%s;" | %s !! style="width:%s;" | %s',
COL_OFFER, H_offer, COL_PRICE, H_price, COL_LIMIT, H_limit
)
t[#t+1] = '|-'
t[#t+1] = '|' .. left_tabber .. string.format(
' || style="text-align:center;" | %s %s %s || style="text-align:center;" | %s',
fileTag(token_icon, { size="25px" }),
tostring(price), token_name,
tostring(limit)
)
-- Currencies unten anhängen
if has_items(ex.currencies) then
for _,it in ipairs(ex.currencies) do
local offer = string.format('%s %s',
fileTag(it.icon or "", { size="30px" }),
item_label(it.item, it.amount or 1)
)
t[#t+1] = '|-'
t[#t+1] = string.format(
'| %s || style="text-align:center;" | %s %s %s || style="text-align:center;" | %s',
offer,
fileTag(token_icon, { size="25px" }),
tostring(it.price or 0), token_name,
tostring(it.limit or "")
)
end
end
t[#t+1] = '|}'
t[#t+1] = references_tag()
return '\n' .. table.concat(t, '\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 = {}
t[#t+1] = '{| class="article-table" style="font-size:14px;"'
t[#t+1] = "! Angebot !! Menge"
for _,it in ipairs(list) do
t[#t+1] = "|-"
t[#t+1] = 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
t[#t+1] = "|}"
return table.concat(t, "\n")
end
-- ========= Event-Dispatcher =========
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
local last = path[#path] and tostring(path[#path]) or ""
v = autotr(last, v)
return tostring(v)
end
-- Fallback: 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 =========
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)
local last = path[#path] and tostring(path[#path]) or ""
v = autotr(last, v)
return tostring(v or "")
end
-- ========= main =========
function M.main(frame)
FRAME = frame -- für frame:extensionTag('tabber', ...)
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