Difference between revisions of "Module:Loot table"
Jump to navigation
Jump to search
Demcookies (talk | contribs) (Fix change html / wikitext parsing.) |
Demcookies (talk | contribs) (Fix changes output from raw html to html with 'A' tags (images are still html but inside the wikitext template string) as wikitext, similar to Module:Img) |
||
| Line 10: | Line 10: | ||
---@return mw.html # The message wrapped in a mw.html 'DIV' element as red text. | ---@return mw.html # The message wrapped in a mw.html 'DIV' element as red text. | ||
local function createErrorMessage(message) | local function createErrorMessage(message) | ||
| − | + | local e = mw.html.create("div") | |
| − | + | :css("color", "red") | |
| − | + | :wikitext(message) | |
| − | + | return e | |
end | end | ||
| Line 21: | Line 21: | ||
---@return string|mw.html # The source type ("monster" or "location"), or mw.html element with error message if the source is not found. | ---@return string|mw.html # The source type ("monster" or "location"), or mw.html element with error message if the source is not found. | ||
local function findSourceId(name) | local function findSourceId(name) | ||
| − | + | for _, source in ipairs({ "monster", "location" }) do | |
| − | + | local id = findId._findId({ name, source }) | |
| − | + | if id ~= "id not found" then | |
| − | + | return id, source | |
| − | + | end | |
| − | + | end | |
| − | + | local errorMessage = createErrorMessage("No monster or location named '" .. name .. "'. The Module:Loot/data may be outdated.") | |
| − | + | return nil, errorMessage | |
| − | |||
end | end | ||
| Line 46: | Line 45: | ||
---@return string? # The locationId where the item was found, or nil if the item is not found. | ---@return string? # The locationId where the item was found, or nil if the item is not found. | ||
local function getFirstLootMatch(loot, id) | local function getFirstLootMatch(loot, id) | ||
| − | + | id = tostring(id) | |
| − | + | for locationId, location in pairs(loot) do | |
| − | + | for sourceId, source in pairs(location) do | |
| − | + | for _, item in ipairs(source) do | |
| − | + | if item.id == id then | |
| − | + | return item, sourceId, locationId | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
end | end | ||
| Line 65: | Line 64: | ||
local function getFirstSourceMatch(loot, id) | local function getFirstSourceMatch(loot, id) | ||
id = tostring(id) | id = tostring(id) | ||
| − | + | for locationId, location in pairs(loot) do | |
| − | + | for sourceId, source in pairs(location) do | |
| − | + | if sourceId == id then | |
| − | + | return source, locationId | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
end | end | ||
| Line 89: | Line 88: | ||
for d = 1, maxDenominator do | for d = 1, maxDenominator do | ||
| − | local n = math.floor(num * d + 0.5) | + | local n = math.floor(num*d + 0.5) |
local approx = n / d | local approx = n / d | ||
local difference = math.abs(num - approx) | local difference = math.abs(num - approx) | ||
| Line 102: | Line 101: | ||
end | end | ||
| − | ---Converts a floating-point number to a approximation of fraction as string. | + | ---Converts a floating-point number to a approximation of fraction as string. |
---@param num number # The floating-point number to convert. | ---@param num number # The floating-point number to convert. | ||
---@param maxDenominator number # The maximum denominator for the fraction. | ---@param maxDenominator number # The maximum denominator for the fraction. | ||
| Line 119: | Line 118: | ||
---@return string # The formatted number as a string. | ---@return string # The formatted number as a string. | ||
local function toFixed(num, digits) | local function toFixed(num, digits) | ||
| − | + | digits = math.max(0, math.floor(digits)) | |
| − | + | local formatted = string.format("%." .. digits .. "f", num) | |
| − | + | formatted = formatted:gsub("%.?0+$", "") | |
| − | + | return formatted | |
end | end | ||
function p.lootTable(frame) | function p.lootTable(frame) | ||
| − | + | local args = frame:getParent().args | |
| − | + | return tostring(p._lootTable(args)) | |
| − | |||
end | end | ||
| Line 191: | Line 189: | ||
end | end | ||
return e:done() | return e:done() | ||
| + | end | ||
| + | |||
| + | local function createImgText(name, src, alt, width, height) | ||
| + | return string.format( | ||
| + | '[[%s|<img src="%s" alt="%s"%s%s>]]', | ||
| + | name, | ||
| + | src, | ||
| + | alt, | ||
| + | width and ' width="' .. width .. '"' or "", | ||
| + | height and ' heigth="' .. heigth .. '"' or "" | ||
| + | ) | ||
end | end | ||
| Line 200: | Line 209: | ||
return "1" | return "1" | ||
end | end | ||
| − | + | return item.value and lang:formatNum(item.value) or "-" | |
end | end | ||
| − | + | ||
---Formats the Item drop quantities as string. | ---Formats the Item drop quantities as string. | ||
---@param item Item # Item table from Module:Loot/data | ---@param item Item # Item table from Module:Loot/data | ||
---@return string # The formatted quantity range as string. | ---@return string # The formatted quantity range as string. | ||
local function createQuantityText(item) | local function createQuantityText(item) | ||
| − | + | if item.minAmount == item.maxAmount then | |
| − | + | return tostring(item.minAmount) | |
| − | + | end | |
| − | + | return lang:formatNum(item.minAmount) .. " - " .. lang:formatNum(item.maxAmount) | |
end | end | ||
| Line 223: | Line 232: | ||
src = src:sub(1, 1) ~= "/" and "/" .. src or src | src = src:sub(1, 1) ~= "/" and "/" .. src or src | ||
local imageHtml = mw.html.create("td") | local imageHtml = mw.html.create("td") | ||
| − | + | :wikitext(createImgText( | |
| − | + | item2.name, | |
| − | + | "https://www.play.idlescape.com" .. src, | |
| − | + | item2.name .. (item2.extraTooltip and "\n" .. item2.extraTooltip or ""), | |
| − | + | args.width or 45, | |
| − | + | args.height or nil | |
| − | + | )) | |
local itemHtml = mw.html.create("td") | local itemHtml = mw.html.create("td") | ||
| − | :tag("a") | + | :wikitext('[[' .. item2.name .. ']]') |
| + | --[[:tag("a") | ||
:attr("href", tostring(mw.uri.localUrl(item2.name))) | :attr("href", tostring(mw.uri.localUrl(item2.name))) | ||
:attr("title", item2.name) | :attr("title", item2.name) | ||
:wikitext(item2.name) | :wikitext(item2.name) | ||
| − | :done() | + | :done()]] |
local quantityHtml = mw.html.create("td") | local quantityHtml = mw.html.create("td") | ||
:wikitext(createQuantityText(item)) | :wikitext(createQuantityText(item)) | ||
| Line 245: | Line 255: | ||
:wikitext(item2.tradeable and "Yes" or "No") | :wikitext(item2.tradeable and "Yes" or "No") | ||
local leagueHtml = mw.html.create("td") | local leagueHtml = mw.html.create("td") | ||
| − | + | --TODO: Add indicator if league is inactive, the field is present in | |
| − | + | -- leagueList.ts / Module:Leagues/data but some are incorrect | |
if item.allowedLeagues then | if item.allowedLeagues then | ||
for _, leagueId in ipairs(item.allowedLeagues) do | for _, leagueId in ipairs(item.allowedLeagues) do | ||
local league = leagueData[tostring(leagueId)] | local league = leagueData[tostring(leagueId)] | ||
if league then | if league then | ||
| − | + | leagueHtml:wikitext(createImgText( | |
| − | + | league.name, | |
| − | + | "https://www.play.idlescape.com" .. league.icon, | |
| − | + | league.name, | |
| − | + | args.width or 45, | |
| − | + | args.height or nil | |
| − | + | )) | |
else | else | ||
leagueHtml:wikitext("?") | leagueHtml:wikitext("?") | ||
| Line 276: | Line 286: | ||
tableHtml:node(rowHtml) | tableHtml:node(rowHtml) | ||
| − | + | end | |
| − | + | return tableHtml | |
end | end | ||
return p | return p | ||
Revision as of 17:16, 3 April 2025
local p = {}
local findId = require("Module:FindId")
local lootData = mw.loadData("Module:Loot/data")
local itemData = mw.loadData("Module:Items/data")
local leagueData = mw.loadData("Module:Leagues/data")
---Creates a mw.html 'DIV' element containing an "error" message.
---@param message string # Message as wikitext.
---@return mw.html # The message wrapped in a mw.html 'DIV' element as red text.
local function createErrorMessage(message)
local e = mw.html.create("div")
:css("color", "red")
:wikitext(message)
return e
end
---Finds the source ID for a given name.
---@param name string # The name of the source.
---@return number|nil # The source ID (monsterID or locationID), or nil if the source is not found.
---@return string|mw.html # The source type ("monster" or "location"), or mw.html element with error message if the source is not found.
local function findSourceId(name)
for _, source in ipairs({ "monster", "location" }) do
local id = findId._findId({ name, source })
if id ~= "id not found" then
return id, source
end
end
local errorMessage = createErrorMessage("No monster or location named '" .. name .. "'. The Module:Loot/data may be outdated.")
return nil, errorMessage
end
---@class Item
---@field allowedLeagues? number[] # The leagues in which the item can be found. Found in all leagues if nil.
---@field id integer # The item ID.
---@field chance number # The base chance of the item being dropped.
---@field minAmount integer # The minimum base amount of the item that can be dropped.
---@field maxAmount integer # The maximum base amount of the item that can be dropped.
---Finds the first item in the loot table that matches the given item ID.
---@param loot table # The loot table from Module:Loot/data.
---@param id string|number # The ID of the item to find.
---@return Item? # The item data, or nil if the item is not found.
---@return string? # The monsterId where the item was found ("-1" for locations), or nil if the item is not found.
---@return string? # The locationId where the item was found, or nil if the item is not found.
local function getFirstLootMatch(loot, id)
id = tostring(id)
for locationId, location in pairs(loot) do
for sourceId, source in pairs(location) do
for _, item in ipairs(source) do
if item.id == id then
return item, sourceId, locationId
end
end
end
end
end
---Finds the first source in the loot table that matches the given sourceId.
---@param loot table # The loot table from Module:Loot/data.
---@param id string|number # The ID of the source to find.
---@return Item[]? # The source data, or nil if the source is not found.
---@return string? # The locationId where the source was found, or nil if the source is not found.
local function getFirstSourceMatch(loot, id)
id = tostring(id)
for locationId, location in pairs(loot) do
for sourceId, source in pairs(location) do
if sourceId == id then
return source, locationId
end
end
end
end
--TODO: Add option to show percentages as fractions
---Converts a floating-point number to a approximation of fraction.
---@param num number # The floating-point number to convert.
---@param maxDenominator number # The maximum denominator for the fraction.
---@return number # The numerator of the fraction.
---@return number # The denominator of the fraction.
local function floatToFraction(num, maxDenominator)
local sign = num < 0 and -1 or 1
num = math.abs(num)
local bestNumerator, bestDenominator = 1, 1
local minDifference = math.huge
for d = 1, maxDenominator do
local n = math.floor(num*d + 0.5)
local approx = n / d
local difference = math.abs(num - approx)
if difference < minDifference then
bestNumerator, bestDenominator = n, d
minDifference = difference
end
end
return sign * bestNumerator, bestDenominator
end
---Converts a floating-point number to a approximation of fraction as string.
---@param num number # The floating-point number to convert.
---@param maxDenominator number # The maximum denominator for the fraction.
---@return string # The fraction as string ('numerator/denominator').
local function floatToFractionString(num, maxDenominator)
local numerator, denominator = floatToFraction(num, maxDenominator)
if denominator == 1 then
return tostring(numerator)
end
return string.format("%d/%d", numerator, denominator)
end
---Formats a number up to a given number of decimal places, removing trailing zeros.
---@param num number # The number to format.
---@param digits number # The number of decimal places to include (must be a non-negative integer).
---@return string # The formatted number as a string.
local function toFixed(num, digits)
digits = math.max(0, math.floor(digits))
local formatted = string.format("%." .. digits .. "f", num)
formatted = formatted:gsub("%.?0+$", "")
return formatted
end
function p.lootTable(frame)
local args = frame:getParent().args
return tostring(p._lootTable(args))
end
function p._lootTable(args)
local sourceName = args.name or args.title or mw.title.getCurrentTitle().text
local sourceId, sourceTypeOrError = findSourceId(sourceName)
if not sourceId then
return sourceTypeOrError
end
local loot = getFirstSourceMatch(lootData, sourceId)
if not loot then
local div = mw.html.create("div")
:css("color", "red")
:wikitext("No loot found for monster or location named '" .. sourceName ..
"' The Module:Loot/data may be outdated.")
return div
end
local lang = mw.language.getContentLanguage()
---@param name string # Wikitext.
---@return mw.html # 'TH' element.
local function createTh(name)
local th = mw.html.create("th")
:addClass("headerSort")
:attr("tabindex", 0)
:attr("title", "Sort ascending")
:wikitext(name)
return th
end
---@param names string[] # Array of wikitext.
---@return mw.html # 'TR' element containing names as 'TH' elements.
local function createThs(names)
local tr = mw.html.create("tr")
for _, name in ipairs(names) do
tr:node(createTh(name))
end
return tr
end
---Creates a 'A' element containing an image.
---@param name string # The name of the item.
---@param src string # The URI to the item image.
---@param width? string|integer # The width of the item image as integer.
---@param height? string|integer # The height of the item image as integer.
---@return mw.html # mw.html 'A' element containing an image.
local function createImg(name, src, alt, width, height)
local e = mw.html.create("a")
:attr("href", tostring(mw.uri.localUrl(name)))
:attr("title", name)
:tag("img")
:attr("src", src)
:attr("alt", alt)
if width then
e:attr("width", width)
end
if height then
e:attr("height", height)
end
return e:done()
end
local function createImgText(name, src, alt, width, height)
return string.format(
'[[%s|<img src="%s" alt="%s"%s%s>]]',
name,
src,
alt,
width and ' width="' .. width .. '"' or "",
height and ' heigth="' .. heigth .. '"' or ""
)
end
---Formats the value of an item as string.
---@param item table # Item table from Module:Items/data
---@return string # The formatted value of the item as string, or '-' if no value.
local function createValueText(item)
if item.id == 1 then -- Gold
return "1"
end
return item.value and lang:formatNum(item.value) or "-"
end
---Formats the Item drop quantities as string.
---@param item Item # Item table from Module:Loot/data
---@return string # The formatted quantity range as string.
local function createQuantityText(item)
if item.minAmount == item.maxAmount then
return tostring(item.minAmount)
end
return lang:formatNum(item.minAmount) .. " - " .. lang:formatNum(item.maxAmount)
end
local tableHtml = mw.html.create("table")
:addClass("wikitable sortable jquery-tablesorter")
:node(createThs({ "Image", "Item", "Quantity", "Rarity", "Value", "Tradeable", "Leagues" }))
for _, item in ipairs(loot) do
local item2 = itemData[tostring(item.id)]
--IS itemList.ts has some incorrect URIs, e.g. for Feather
local src = item2.itemImage
src = src:sub(1, 1) ~= "/" and "/" .. src or src
local imageHtml = mw.html.create("td")
:wikitext(createImgText(
item2.name,
"https://www.play.idlescape.com" .. src,
item2.name .. (item2.extraTooltip and "\n" .. item2.extraTooltip or ""),
args.width or 45,
args.height or nil
))
local itemHtml = mw.html.create("td")
:wikitext('[[' .. item2.name .. ']]')
--[[:tag("a")
:attr("href", tostring(mw.uri.localUrl(item2.name)))
:attr("title", item2.name)
:wikitext(item2.name)
:done()]]
local quantityHtml = mw.html.create("td")
:wikitext(createQuantityText(item))
local chanceHtml = mw.html.create("td")
:wikitext(toFixed(item.chance * 100, 3) .. "%")
local priceHtml = mw.html.create("td")
:wikitext(createValueText(item2))
local tradeableHtml = mw.html.create("td")
:wikitext(item2.tradeable and "Yes" or "No")
local leagueHtml = mw.html.create("td")
--TODO: Add indicator if league is inactive, the field is present in
-- leagueList.ts / Module:Leagues/data but some are incorrect
if item.allowedLeagues then
for _, leagueId in ipairs(item.allowedLeagues) do
local league = leagueData[tostring(leagueId)]
if league then
leagueHtml:wikitext(createImgText(
league.name,
"https://www.play.idlescape.com" .. league.icon,
league.name,
args.width or 45,
args.height or nil
))
else
leagueHtml:wikitext("?")
end
end
else
leagueHtml:wikitext("All")
end
local rowHtml = mw.html.create("tr")
:node(imageHtml)
:node(itemHtml)
:node(quantityHtml)
:node(chanceHtml)
:node(priceHtml)
:node(tradeableHtml)
:node(leagueHtml)
tableHtml:node(rowHtml)
end
return tableHtml
end
return p