Zum Inhalt springen
Das Halloween-Event (Süßes oder Saures) ist beendet. Du kannst verbleibende Kürbisse noch im Hexenhütte eintauschen.

Modul:Firestone/Event

Aus Firestone Idle Rpg Wiki

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

-- Modul:Firestone/Event
local Util = require('Modul:Firestone/Util')
local I18n = require('Modul:Firestone/I18n')

local E = {}

local COL_OFFER, COL_PRICE, COL_LIMIT = "50%", "25%", "25%"
local DAY = Util.DAY or 86400

-- alle Event-Daten zusammenführen (calendar + mini + special)
local function loadTable(name)
    local ok, data = pcall(mw.loadData, name)
    if ok and type(data) == 'table' then return data end
    ok, data = pcall(require, name)
    if ok and type(data) == 'table' then return data end
    return {}
end

local function mergedEvents()
    local all = {}
    local sources = {
        'Modul:Firestone/Data/Events',
        'Modul:Firestone/Data/Events/Mini',
        'Modul:Firestone/Data/Events/Special',
    }
    for _,name in ipairs(sources) do
        local t = loadTable(name)
        for k,v in pairs(t) do
            all[k] = v
        end
    end
    return all
end

local EVENTS = mergedEvents()
local FRAME

-- i18n helpers
local function evI18n()
    return (I18n.get().events or {})
end

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

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

local function header_labels(evkey)
    return ev_label(evkey, "offer"), ev_label(evkey, "price"), ev_label(evkey, "limit")
end

-- auto-translation wie vorher
local function autotr(key_name, value)
    if type(value) ~= "string" then return value end
    local k = Util.norm(key_name)
    local i18 = I18n.get()
    local m1 = (i18.i18n or {})[k]
    local v  = m1 and m1[Util.norm(value)]
    if v then return v end
    if k == "type" then
        local m2 = (((i18.events or {}).infobox) or {}).type or {}
        return m2[Util.norm(value)] or value
    end
    return value
end

local function ev_notice_icon_wikitext(ev)
    local file = ev.currency and ev.currency.icon
    if not file or file == "" then return "" end
    local img = Util.fileTag(file, { size = "24px", param = "frameless|link=" })
    return '<span class="fs-ic">' .. img .. '</span>'
end

local function ev_pagelink(ev, evkey, label)
    local page = ev.page
    if page and page ~= "" then
        return string.format("[[%s|%s]]", page, label)
    end
    return label
end

-- Zahlenformatierung (verkürzt, aber wie vorher)
local function parse_sci(v)
    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

local function i18n_big_units()
    local N = (I18n.get().numbers or {})
    return {
        {1e12, N.trillion_sing,  N.trillion_plur},
        {1e9,  N.billion_sing,   N.billion_plur},
        {1e6,  N.million_sing,   N.million_plur},
        {1e3,  N.thousand_sing,  N.thousand_plur},
    }
end

local function format_big(v)
    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

local function ref_gear_power(v)
    if Util.isEmpty(v) then return "" end
    local phr = (evI18n().phrases or {}).req_gear_power
    if Util.isEmpty(phr) then return "" end
    local name = "gear-" .. tostring(v):gsub("%s","")
    return string.format('<ref name="%s">%s</ref>', name, (phr:gsub("$1", format_big(v))))
end

local function htag(level, text)
    if Util.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 Util.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 Util.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 Util.fileTag(file, { param = "thumb|"..legend })
    elseif which=="banner" then
        return Util.fileTag(ev.banner, { param="thumb" })
    end
    return ""
end

local function event_link(ev, evkey, which)
    if which=="type" then
        local Eii = evI18n()
        local page  = (Eii.links and Eii.links.type and Eii.links.type[ev.type or ""])
                       or (Eii.links and Eii.links.type) or "Calendar Events"
        local map   = (Eii.infobox and Eii.infobox.type) or {}
        local label = map[Util.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 Util.hasItems(H) then return "" end

    local alt_gp = (evI18n().phrases or {}).alt_platform or "auf Google Play"

    local function fmt_range_de(s1, s2)
        return string.format("%s bis %s", Util.formatDateDe(s1), Util.formatDateDe(s2))
    end

    local function fmt_alt_platform(val)
        local y1,m1,d1,y2,m2,d2 =
            tostring(val or ""):match("^%s*(%d%d%d%d)%-(%d%d)%-(%d%d)%s+bis%s+(%d%d%d%d)%-(%d%d)%-(%d%d)%s*$")
        if y1 then
            return fmt_range_de(y1.."-"..m1.."-"..d1, y2.."-"..m2.."-"..d2)
        end
        return tostring(val or "")
    end

    local out = {}
    for _,h in ipairs(H) do
        local year = tostring(h.year or "")
        local s    = h.start or ""
        local e    = h["end"] or ""
        local line = string.format("* %s: %s", year, fmt_range_de(s, e))
        if h.alt_platform then
            line = line .. string.format(" (%s %s)", fmt_alt_platform(h.alt_platform), alt_gp)
        end
        table.insert(out, line)
    end
    return table.concat(out, "\n")
end

local function item_label(key, amount)
    local I = (I18n.get().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 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 build_tabber_content_for(list)
    local out = {}
    for _,it in ipairs(list) do
        local label   = Util.prettyRange(it.range or "")
        local note    = ref_gear_power(it.req_gear_power)
        local content = string.format(
            "%s %s%s",
            Util.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

local function group_label(kind)
    local G = (evI18n().group_labels or {})
    return G[kind] or kind
end

local function build_avatar_table(list, token_icon, token_name)
    local t = {}
    t[#t+1] = '{| class="article-table" style="font-size:14px;"'
    t[#t+1] = '! colspan="2" | Avatar\n! Preis'
    for _,it in ipairs(list) do
        t[#t+1] = '|-'
        t[#t+1] = string.format(
            '| %s || %s || %s %s %s',
            Util.fileTag(it.file,  { size="50px" }),
            it.title or "",
            Util.fileTag(token_icon, { size="25px" }),
            tostring(it.cost or 0),
            token_name
        )
    end
    t[#t+1] = '|}'
    return table.concat(t, "\n")
end

local function event_avatars(ev, evkey)
    local AV = ev.avatars
    if not Util.hasItems(AV) then return "" end

    local token_icon = (ev.currency or {}).icon or ""
    local token_name = ev_label(evkey, "currencies")

    local years = {}
    for y,_ in pairs(AV) do years[#years+1] = tostring(y) end
    table.sort(years, function(a,b) return a > b end)

    local parts = {}
    for _,year in ipairs(years) do
        local list = AV[year] or AV[tonumber(year)]
        if Util.hasItems(list) then
            local table_markup = build_avatar_table(list, token_icon, token_name)
            parts[#parts+1] = "|-|" .. year .. "=\n" .. table_markup
        end
    end

    return tabber_render_from_content(table.concat(parts, "\n"))
end

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 (Util.hasItems(ex.chests_by_level)
         or Util.hasItems(ex.chests_by_stars)
         or Util.hasItems(ex.chests_by_oracle)
         or Util.hasItems(ex.currencies)) then
      return ""
    end

    local outer_parts = {}
    local function add_group(kind, list)
        if not Util.hasItems(list) then return end
        local inner_content = build_tabber_content_for(list)
        local inner_tabber  = tabber_render_from_content(inner_content)
        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)

    local left_tabber = tabber_render_from_content(table.concat(outer_parts, "\n"))

    local function pick_price_limit()
        if Util.hasItems(ex.chests_by_level)  then return ex.chests_by_level[1].price or 0,  ex.chests_by_level[1].limit or ""  end
        if Util.hasItems(ex.chests_by_stars)  then return ex.chests_by_stars[1].price or 0,  ex.chests_by_stars[1].limit or ""  end
        if Util.hasItems(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()

    local t = {}
    local H_offer, H_price, H_limit = header_labels(evkey)
    t[#t+1] = '{| class="article-table fs-exchange" 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',
        Util.fileTag(token_icon, { size="25px" }),
        tostring(price), token_name,
        tostring(limit)
    )

    if Util.hasItems(ex.currencies) then
        for _,it in ipairs(ex.currencies) do
            local offer = string.format('%s %s',
                Util.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,
                Util.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

-- Zeitfenster eines Events
local function event_window(ev)
    local S = ev.schedule or ev
    local s = S.start_date; if Util.isEmpty(s) then return nil end

    local st = Util.parseIsoDate(s); if not st then return nil end

    local en_excl, en_incl
    if not Util.isEmpty(S.end_date) then
        en_incl = Util.parseIsoDate(S.end_date)
        if not en_incl then return st, nil, nil end
        en_excl = en_incl + DAY
    else
        local d = tonumber(S.duration_days) or 14
        en_excl = st + d * DAY
        en_incl = en_excl - DAY
    end

    return st, en_excl, en_incl
end

-- Event wählen, das jetzt laufen/gleich startet
local function pick_notice_eventkey()
    local cfg = (evI18n().notice or {})
    local pre  = tonumber(cfg.pre_days)  or 7
    local post = tonumber(cfg.post_days) or 3
    local now = os.time()

    local running, upcoming, ended_recent = {}, {}, {}

    for k, ev in pairs(EVENTS) do
        local st, en_excl = event_window(ev)
        if st and en_excl then
            if now >= st and now < en_excl then
                table.insert(running, { k=k, st=st, en=en_excl })
            elseif now >= st - pre*DAY and now < st then
                table.insert(upcoming, { k=k, st=st, en=en_excl })
            elseif now >= en_excl and now < en_excl + post*DAY then
                table.insert(ended_recent, { k=k, st=st, en=en_excl })
            end
        end
    end

    local function by_end(a,b)   return a.en < b.en end
    local function by_start(a,b) return a.st < b.st end
    if #running      > 0 then table.sort(running,      by_end)   ; return running[1].k      end
    if #upcoming     > 0 then table.sort(upcoming,     by_start) ; return upcoming[1].k     end
    if #ended_recent > 0 then table.sort(ended_recent, by_end)   ; return ended_recent[1].k end
    return nil
end

local function event_notice(ev, evkey)
    local phrases = (evI18n().phrases or {})
    local PH_RUNNING = phrases.event_notice
    local PH_ENDED   = phrases.event_ended

    if Util.isEmpty(PH_RUNNING) then return "" end

    local st, en_excl, en_incl = event_window(ev); if not st or not en_excl then return "" end

    local cfg  = (evI18n().notice or {})
    local pre  = tonumber(cfg.pre_days)  or 7
    local post = tonumber(cfg.post_days) or 3
    local now  = os.time()

    local show_from = st - pre  * DAY
    local show_till = en_excl + post * DAY
    if now < show_from or now >= show_till then
        return ""
    end

    local name_label = ev_label(evkey, "eventname")
    if Util.isEmpty(name_label) then name_label = ev_name(evkey) end

    local name = ev_pagelink(ev, evkey, name_label)

    local ic = ev_notice_icon_wikitext(ev)

    local text
    if now < en_excl then
        text = PH_RUNNING
            :gsub("$1", name)
            :gsub("$2", Util.formatDateDe(st))
            :gsub("$3", Util.formatDateDe(en_excl))
    else
        if Util.isEmpty(PH_ENDED) then return "" end
        local curr = ev_label(evkey, "currencies")
        local shop = ev_label(evkey, "ex_shop")
        if Util.isEmpty(curr) or Util.isEmpty(shop) then return "" end
        text = PH_ENDED
          :gsub("$1", name)
          :gsub("$4", curr)
          :gsub("$5", shop)
    end

    local prefix = (ic ~= "" and (ic .. " ") or "")
    local suffix = (ic ~= "" and (" " .. ic) or "")

    return string.format(
        '<div class="fs-notice" data-ev="%s">%s%s%s</div>',
        mw.text.encode(evkey),
        prefix, text, suffix
    )
end

local function get_event(evkey)
    local real = Util.pickKey(EVENTS, evkey)
    return real and EVENTS[real] or nil, real or evkey
end

function E.handle(frame, args)
    FRAME = frame
    local evkey = args[2]

    if evkey and Util.norm(evkey) == "notice" then
        local pick = pick_notice_eventkey()
        if not pick then return "" end
        local ev, r = get_event(pick); if not ev then return "" end
        return event_notice(ev, r)
    end

    if Util.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 Util.isEmpty(a3) then
        return ev_name(realkey)
    end

    if a3=="notice"  then return event_notice(ev, realkey)
    elseif 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, realkey)
    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

    if a3 == "currency"   then return ev_label(realkey, "currency")  end
    if a3 == "currencies" then return ev_label(realkey, "currencies") end

    local path, i = {}, 3
    while args[i] do
        table.insert(path, args[i])
        i = i + 1
    end
    local v = Util.deepGet(ev, path)
    if v ~= nil then
        local last = path[#path] and tostring(path[#path]) or ""
        v = autotr(last, v)
        return tostring(v)
    end

    local last = Util.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 Util.isEmpty(lbl) then return lbl end
    local Uu = (evI18n().unlocks or {})[last]
    if Uu then return Uu end

    return ""
end

return E