Module:Sandbox/Desynthesis results

From Final Fantasy XIV Online Wiki
Jump to navigation Jump to search
Documentation for Module:Sandbox/Desynthesis results does not exist yet [create] (How does this work?)
function renderQty(min, max)
    if min == max then
        return tostring(min)
    end
    return ("%d–%d"):format(min, max)
end

local function itemDataFromID(id)
    local results = mw.smw.ask({
        ("[[Has context::Item]][[Has internal ID::%d]]"):format(id),
        "?=Pagename#",
        "?Has context",
        "?Has game icon#",
        "?Has game icon frame type",
        "?Has canonical name",
        "?Has dye channels",
        limit = 1,
        link = "none",
    })
    return results and results[1] or nil
end

local icon = require("Module:Icon")

local function itemIconFromData(data)
    if not data then
        return ""
    end
    return tostring(icon.gameIconWithLabel({
        icon = data["Has game icon"],
        link = data["Pagename"],
        type = data["Has context"],
        dyeCount = tonumber(data["Has dye channels"]) or 0,
        size = "small",
        text = data["Has canonical name"],
    }))
end

local p = {}

function p.renderTable(id)
    id = tonumber(id)

    -- Desynthesis data is huge, so it's split up into multiple JSON files
    -- that we need to be able to handle. On the wiki these are under
    -- Module:Desynthesis with subpages that group every 1000 item IDs where
    -- IDs 1 through 1000 go in 001000.json, 1001-2000 go in 002000.json, etc.
    --
    -- Attempt to load the appropriate JSON file; if there is no such file, or
    -- the file doesn't contain data for this item, return an error.
    local name = ("Module:Desynthesis/%03d000.json"):format(math.floor((id - 1) / 1000) + 1)
    local success, dataOrError = pcall(
        mw.loadJsonData,
        name
    )
    local sourceData
    if success then
        sourceData = dataOrError.Sources[id]
    end -- otherwise, leave sourceData nil

    if not sourceData then
        return ("No data yet. (via [https://xivstats.com/desynth?source=%s xivstats.com])"):format(id)
    end

    local rewards = sourceData.Rewards
    -- shallow copy into a new table so we can sort it
    local mutableRewards = {}
    for k, v in ipairs(rewards) do
        mutableRewards[k] = v
    end
    mw.dumpObject(mutableRewards)

    -- sort by chance of obtaining, descending
    table.sort(mutableRewards, function(a, b) return a.Pct > b.Pct end)

    local result = "{| class=\"desynthesis-results-table stdt__table_v2 crafting sortable\"\n! Item\n! Quantity\n! Chance\n"
    for _, reward in ipairs(mutableRewards) do
        local data = itemDataFromID(reward.Id)
        if data and data["Pagename"] then
            mw.smw.set({ ["Has desynthesis result"] = data["Pagename"] })
        end
        result = result .. ("|-\n| %s\n| %s\n| %.2f%%\n"):format(
            itemIconFromData(data),
            renderQty(reward.Min, reward.Max),
            reward.Pct * 100
        )
    end

    result = result .. ("|- class=sortbottom\n! align=center colspan=3 | Based on %d desynth attempts • Data provided by [https://xivstats.com/desynth?source=%s xivstats.com]\n|}"):format(sourceData.Records, id)
    return result
end

function p.main(frame)
    local id = frame.args.id or ""
    local header = frame.args.header or ""

    if id == "" then
        return "<span class=error>Error: no item ID provided.</span>"
    end

    local table = p.renderTable(id)
    if not table then
        if header == "" then
            return ("<span class=error>Error: No data for desynthesis source %d</span>"):format(id)
        else
            return ""
        end
    end

    if header ~= "" then
        header = frame:preprocess(header) .. "\n"
    end
    return header .. table
end

return p