Difference between revisions of "Module:Drop Sources"
Jump to navigation
Jump to search
m (HASH moved page Module:Drop sources to Module:Drop Sources without leaving a redirect) |
Demcookies (talk | contribs) (Fix rename [Toggle all drops] to [Collapse] / [Expand] and style to look more like a button. Add darker background to rows that are from specific leagues) |
||
| (6 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local findId = require("Module:FindId") | local findId = require("Module:FindId") | ||
| − | local | + | local lootData = mw.loadData("Module:Loot/data") |
| − | local | + | local itemsData = mw.loadData("Module:Items/data") |
| + | local leaguesData = mw.loadData("Module:Leagues/data") | ||
| + | local locationData = mw.loadData("Module:Location/data") | ||
local monstersData = mw.loadData("Module:Monsters/data") | local monstersData = mw.loadData("Module:Monsters/data") | ||
| + | local monstersStatsData = mw.loadData("Module:Monsters stats/data") | ||
| − | local function | + | |
| − | + | ---@class LootData | |
| − | local | + | ---@field id number The ID of the item. |
| − | local | + | ---@field chance number The chance of the item dropping. |
| − | + | ---@field minAmount number The minimum amount of the item that can drop. | |
| − | + | ---@field maxAmount number The maximum amount of the item that can drop. | |
| − | + | ---@field allowedLeagues number[]|nil The IDs of the leagues that allow the item to drop, or nil if allowed everywhere. | |
| − | + | ||
| − | + | ||
| − | + | ---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):gsub("%.?0+$", "") | ||
| + | return formatted | ||
| + | end | ||
| + | |||
| + | ---Formats the given number range. | ||
| + | ---@param min number The minimum of the range. | ||
| + | ---@param max number The maximum of the range. | ||
| + | ---@return string # The formatted range as string. | ||
| + | local function toNumberRange(min, max) | ||
| + | local lang = mw.language.getContentLanguage() | ||
| + | if min == max then | ||
| + | return lang:formatNum(min) | ||
| + | end | ||
| + | return lang:formatNum(min) .. "–" .. lang:formatNum(max) | ||
| + | end | ||
| + | |||
| + | ---Calculates the length of a table. | ||
| + | ---@param t table | ||
| + | ---@return number | ||
| + | local function getTableLength(t) | ||
| + | local count = 0 | ||
| + | for _ in pairs(t) do | ||
| + | count = count + 1 | ||
| + | end | ||
| + | return count | ||
| + | end | ||
| + | |||
| + | ---Creates a deep copy of a table. | ||
| + | ---@param t table | ||
| + | ---@return table | ||
| + | local function copyTable(t) | ||
| + | local copy = {} | ||
| + | for k, v in pairs(t) do | ||
| + | if type(v) == "table" then | ||
| + | copy[k] = copyTable(v) | ||
| + | else | ||
| + | copy[k] = v | ||
end | end | ||
end | end | ||
| − | table. | + | return copy |
| − | local | + | end |
| − | + | ||
| − | + | ---Creates an Wikitext link with an image. | |
| − | + | ---@param title string The title of the link. | |
| − | + | ---@param imageAttributes table The html attributes for the image. | |
| − | + | ---@return string # Wikitext link with the image. | |
| − | + | local function createWikitextImage(title, imageAttributes) | |
| − | + | local img = mw.html.create("img") | |
| − | + | :attr(imageAttributes) | |
| − | + | img = tostring(img):gsub("<img(.-) */>", "<img%1>") | |
| − | + | return string.format('[[%s|%s]]', title, img) | |
| − | + | end | |
| − | + | ||
| + | ---Finds locations, sources and `LootData` for a given item ID. | ||
| + | ---{ locationId = { sourceId = { LootData1, LootData2, ... }, ... }, ... } | ||
| + | ---@param itemId string|number The ID of the item to search for. | ||
| + | ---@return table<string, table<string, LootData[]>>|nil # A table containing locations, sources and `LootData` for the item, or nil if not found. | ||
| + | local function getSources(itemId) | ||
| + | itemId = tostring(itemId) | ||
| + | local sources = {} | ||
| + | for locationId, location in pairs(lootData) do | ||
| + | for sourceId, source in pairs(location) do | ||
| + | for _, drop in ipairs(source) do | ||
| + | if tostring(drop.id) == itemId then | ||
| + | sources[locationId] = sources[locationId] or {} | ||
| + | sources[locationId][sourceId] = sources[locationId][sourceId] or {} | ||
| + | table.insert(sources[locationId][sourceId], drop) | ||
| + | end | ||
| + | end | ||
end | end | ||
| − | |||
end | end | ||
| − | return | + | return sources |
end | end | ||
| − | local function | + | ---Gets monster or location object based on the source ID and optionally location ID for locations and override monster. |
| − | return tostring( | + | ---@param sourceId string|number The ID of the source, -1 for locations. |
| + | ---@param locationId string|number|nil The ID of the location (optional), required for locations. | ||
| + | ---@return table|nil # The stats of the monster or nil if not found. | ||
| + | local function getSource(sourceId, locationId) | ||
| + | sourceId = tostring(sourceId) | ||
| + | if locationId then | ||
| + | locationId = tostring(locationId) | ||
| + | --If sourceId is "-1", it means we are looking for a location | ||
| + | if sourceId == "-1" then | ||
| + | local location = locationData[locationId] | ||
| + | if location then | ||
| + | return copyTable(location) | ||
| + | end | ||
| + | end | ||
| + | local baseMonster = monstersStatsData[sourceId] | ||
| + | for _, monster in pairs(baseMonster.overrides or {}) do | ||
| + | for _, location in ipairs(monster.locations or {}) do | ||
| + | if location and tostring(location.id) == locationId then | ||
| + | return copyTable(monster) | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | --Tries to get the stats of base monster if not a location or no override is found | ||
| + | -- e.g. no locationId or was given but no ovverides for the monster | ||
| + | return copyTable(monstersStatsData[sourceId]) | ||
end | end | ||
| − | + | ---Converts location-source-loot table to name-sourceStats-loot table. | |
| − | + | ---@param sources table<string, table<string, LootData[]>> # The drop sources for the item. | |
| − | local | + | ---@return table<string, table> # A table containing the sources and their loot data and and optional Invalid IDs table. |
| − | for | + | --[[ |
| − | for | + | |
| − | if | + | return { |
| − | + | ["Source Name"] = { | |
| − | + | _loot = { LootData1, LootData2, ... }, | |
| − | + | _id = sourceId, | |
| − | + | `unpack key-value pairs from Module monstersStatsData or locationData for the source` | |
| − | + | }, | |
| − | + | ..., | |
| + | ["Invalid ID"] = { invalidSourceId1, invalidSourceId2, ... } | ||
| + | } | ||
| + | ]] | ||
| + | local function getSourceList(sources) | ||
| + | local sourceList = {} | ||
| + | for locationId, sources_ in pairs(sources) do | ||
| + | for sourceId, loot in pairs(sources_) do | ||
| + | local source = getSource(sourceId, locationId) | ||
| + | if source then | ||
| + | local name = source.name | ||
| + | sourceList[name] = sourceList[name] or source | ||
| + | sourceList[name]._loot = sourceList[name]._loot or loot | ||
| + | sourceList[name]._id = sourceList[name]._id or sourceId | ||
| + | else | ||
| + | sourceList["Invalid ID"] = sourceList["Invalid ID"] or {} | ||
| + | table.insert(sourceList["Invalid ID"], sourceId) | ||
end | end | ||
end | end | ||
end | end | ||
| − | return | + | return sourceList |
end | end | ||
| − | local function | + | ---Creates a mw.html TR element containing monster and loot data. |
| − | local | + | ---@param name string The name of the monster or location. |
| − | if | + | ---@param src string The source of the image. |
| − | + | ---@param loot LootData The loot data for the item. | |
| + | ---@param imageWidth string|number The width of the image. | ||
| + | ---@param imageHeight string|number The height of the image. | ||
| + | ---@return mw.html|table # mw.html TR element containing monster and loot data. | ||
| + | local function createMonsterLootRow(name, src, loot, imageWidth, imageHeight) | ||
| + | local imageHtml = mw.html.create("td") | ||
| + | :wikitext(createWikitextImage( | ||
| + | name, | ||
| + | { | ||
| + | src = "https://www.play.idlescape.com" .. src, | ||
| + | alt = name, | ||
| + | width = imageWidth, | ||
| + | height = imageHeight | ||
| + | } | ||
| + | )) | ||
| + | local itemHtml = mw.html.create("td") | ||
| + | :wikitext('[[' .. name .. ']]') | ||
| + | local quantityHtml = mw.html.create("td") | ||
| + | :css("text-align", "center") | ||
| + | :wikitext(toNumberRange(loot.minAmount, loot.maxAmount)) | ||
| + | local chanceHtml = mw.html.create("td") | ||
| + | :css("text-align", "center") | ||
| + | :wikitext(toFixed(loot.chance * 100, 3) .. "%") | ||
| + | local leagueHtml = mw.html.create("td") | ||
| + | :css("text-align", "center") | ||
| + | --TODO: Add indicator if league is inactive, the field is present in | ||
| + | -- leagueList.ts / Module:Leagues/data but some are incorrect | ||
| + | if loot.allowedLeagues then | ||
| + | for _, leagueId in ipairs(loot.allowedLeagues) do | ||
| + | local league = leaguesData[tostring(leagueId)] | ||
| + | if league then | ||
| + | leagueHtml:wikitext(createWikitextImage( | ||
| + | league.name, | ||
| + | { | ||
| + | src = "https://www.play.idlescape.com" .. league.icon, | ||
| + | alt = league.name, | ||
| + | width = 30, | ||
| + | height = 30 | ||
| + | } | ||
| + | )) | ||
| + | else | ||
| + | leagueHtml:wikitext("?") | ||
| + | end | ||
| + | end | ||
else | else | ||
| − | + | leagueHtml:wikitext("All") | |
end | end | ||
| + | |||
| + | local rowHtml = mw.html.create("tr") | ||
| + | :node(imageHtml) | ||
| + | :node(itemHtml) | ||
| + | :node(quantityHtml) | ||
| + | :node(chanceHtml) | ||
| + | :node(leagueHtml) | ||
| + | |||
| + | if loot.allowedLeagues then | ||
| + | rowHtml:css("background-color", "rgba(0, 0, 0, 0.15)") | ||
| + | end | ||
| + | |||
| + | return rowHtml | ||
end | end | ||
| − | local function | + | ---Creates a table of drop sources for a given item. |
| − | local | + | ---@param sources table<string, table<string, LootData[]>> # The drop sources for the item. |
| − | + | ---@param imageWidth string|number # The width of the images in the table. | |
| − | + | ---@param imageHeight string|number # The height of the images in the table. | |
| − | + | local function createDropSourceTable(sources, imageWidth, imageHeight) | |
| − | + | ---@param names string[] Array of wikitext. | |
| − | for | + | ---@return mw.html|table # mw.html TR element containing names as TH elements. |
| − | if | + | local function createThs(names) |
| − | + | local tr = mw.html.create("tr") | |
| − | + | for _, name in ipairs(names) do | |
| − | + | tr:node(mw.html.create("th") | |
| − | + | :addClass("headerSort") | |
| − | + | :attr("tabindex", 0) | |
| − | + | :attr("title", "Sort ascending") | |
| − | + | :wikitext(name) | |
| + | ) | ||
| + | end | ||
| + | return tr | ||
| + | end | ||
| + | |||
| + | ---Creates a mw.html SPAN element with optional text, attributes, CSS and class. | ||
| + | ---@param text string|nil The text to be added to the span. | ||
| + | ---@param attr table|nil The attributes to be added to the span. | ||
| + | ---@param css table|nil The CSS styles to be added to the span. | ||
| + | ---@param class string|nil The classes to be added to the span. | ||
| + | ---@return table|mw.html # mw.html SPAN element. | ||
| + | local function createSpan(text, attr, css, class) | ||
| + | local span = mw.html.create("span") | ||
| + | if attr then span:attr(attr) end | ||
| + | if css then span:css(css) end | ||
| + | if class then span:addClass(class) end | ||
| + | if text then span:wikitext(text) end | ||
| + | return span | ||
| + | end | ||
| + | |||
| + | local function createCustomCollapsible(collapsed, className) | ||
| + | return mw.html.create("span") | ||
| + | :attr("id", "mw-customcollapsible-" .. className) | ||
| + | :css("font-weight", "700") | ||
| + | :addClass("mw-collapsible " .. (collapsed and " mw-collapsed" or "")) | ||
| + | :wikitext("[") | ||
| + | :node(createSpan( | ||
| + | collapsed and "Collapse" or "Expand", | ||
| + | { ["role"] = "button" }, | ||
| + | { | ||
| + | --["cursor"] = "pointer", | ||
| + | --["font-weight"] = "700", | ||
| + | ["color"] = "#8cabe6" | ||
| + | }, | ||
| + | "mw-customtoggle-" .. className | ||
| + | )) | ||
| + | :wikitext("]") | ||
| + | end | ||
| + | |||
| + | local tableHtml = mw.html.create("table") | ||
| + | :addClass("wikitable sortable mw-collapsible") | ||
| + | --[[ Adding caption and collapsing the table did sift the content after the table under infobox | ||
| + | (normally it would occupy the space next to it), so captions are disabled for now | ||
| + | :tag("caption") | ||
| + | :wikitext("Monsters and locations") | ||
| + | :done() | ||
| + | ]] | ||
| + | :node(createThs({ " ", "Source", "Quantity", "Rarity", "Leagues" })) | ||
| + | |||
| + | for name, source in pairs(getSourceList(sources)) do | ||
| + | local rows = {} | ||
| + | |||
| + | if name == "Invalid ID" then | ||
| + | table.insert( | ||
| + | rows, | ||
| + | mw.html.create("tr") | ||
| + | :tag("td") | ||
| + | :attr("colspan", 5) | ||
| + | :wikitext("No sources with IDs: " .. table.concat(source, ", ")) | ||
| + | :done() | ||
| + | ) | ||
| + | else | ||
| + | local _loot = source._loot | ||
| + | local multiDrop = getTableLength(_loot) > 1 | ||
| + | local relatedKey = itemsData[tostring(source.relatedKey)] | ||
| + | local relatedKeyImage = relatedKey and relatedKey.itemImage | ||
| + | local src = source.imageOverride or relatedKeyImage or (monstersData[tostring(source._id)] or {}).image | ||
| + | src = src and src:sub(1, 1) ~= "/" and "/" .. src or src | ||
| + | local className = name:gsub(" ", "_"):gsub("[^%w_]", "") | ||
| + | |||
| + | if multiDrop then | ||
| + | table.insert( | ||
| + | rows, | ||
| + | mw.html.create("tr") | ||
| + | :tag("td") | ||
| + | :wikitext(createWikitextImage( | ||
| + | name, | ||
| + | { | ||
| + | src = "https://www.play.idlescape.com" .. src, | ||
| + | alt = name, | ||
| + | width = imageWidth, | ||
| + | height = imageHeight | ||
| + | } | ||
| + | )) | ||
| + | :done() | ||
| + | :tag("td") | ||
| + | :wikitext("[[" .. name .. "]]") | ||
| + | :done() | ||
| + | :tag("td") | ||
| + | :attr("colspan", 3) | ||
| + | :css("text-align", "center") | ||
| + | :addClass("mw-customtoggle-" .. className) | ||
| + | :node(createCustomCollapsible( | ||
| + | false, | ||
| + | className | ||
| + | )) | ||
| + | :node(createCustomCollapsible( | ||
| + | true, | ||
| + | className | ||
| + | )) | ||
| + | :node(createSpan( | ||
| + | "▼", | ||
| + | { ["id"] = "mw-customcollapsible-" .. className }, | ||
| + | { ["float"] = "right" }, | ||
| + | "mw-collapsible" | ||
| + | )) | ||
| + | :node(createSpan( | ||
| + | "▲", | ||
| + | { ["id"] = "mw-customcollapsible-" .. className }, | ||
| + | { ["float"] = "right" }, | ||
| + | "mw-collapsible mw-collapsed" | ||
| + | |||
| + | )) | ||
| + | :done() | ||
| + | ) | ||
end | end | ||
| − | + | ||
| − | + | for _, loot in ipairs(_loot) do | |
| − | + | local row = createMonsterLootRow(name, src, loot, imageWidth, imageHeight) | |
| − | + | if multiDrop then | |
| − | + | row | |
| − | for _, | + | :addClass("mw-collapsible mw-collapsed") |
| − | if | + | :attr("id", "mw-customcollapsible-" .. className) |
| − | |||
| − | |||
| − | |||
| − | |||
end | end | ||
| − | + | table.insert(rows, row) | |
| − | |||
end | end | ||
| + | end | ||
| + | |||
| + | for _, row in ipairs(rows) do | ||
| + | tableHtml:node(row) | ||
end | end | ||
end | end | ||
| − | + | ||
| − | return | + | return tableHtml |
end | end | ||
| + | |||
function p.dropSources(frame) | function p.dropSources(frame) | ||
| − | + | local args = frame:getParent().args or {} | |
| + | return p._dropSources(args) | ||
end | end | ||
| − | function p._dropSources( | + | function p._dropSources(args) |
| − | local itemId = findId._findId({ | + | local name = args["name"] or args["title"] or mw.title.getCurrentTitle().text |
| − | local | + | local itemId = findId._findId({ name, "item" }) |
| − | + | if itemId == "id not found" then | |
| − | + | return "No id found for item '" .. name .. "'." | |
| − | return | + | end |
| + | local sources = getSources(itemId) | ||
| + | if not sources then | ||
| + | return "No sources found for '" .. name .. "', Modules: Loot/data or Monsters_stats/data may be outdated." | ||
| + | end | ||
| + | local html = createDropSourceTable(sources, args["width"] or 30, args["height"] or 30) | ||
| + | return html | ||
end | end | ||
| + | |||
return p | return p | ||
Latest revision as of 21:31, 22 April 2025
local p = {}
local findId = require("Module:FindId")
local lootData = mw.loadData("Module:Loot/data")
local itemsData = mw.loadData("Module:Items/data")
local leaguesData = mw.loadData("Module:Leagues/data")
local locationData = mw.loadData("Module:Location/data")
local monstersData = mw.loadData("Module:Monsters/data")
local monstersStatsData = mw.loadData("Module:Monsters stats/data")
---@class LootData
---@field id number The ID of the item.
---@field chance number The chance of the item dropping.
---@field minAmount number The minimum amount of the item that can drop.
---@field maxAmount number The maximum amount of the item that can drop.
---@field allowedLeagues number[]|nil The IDs of the leagues that allow the item to drop, or nil if allowed everywhere.
---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):gsub("%.?0+$", "")
return formatted
end
---Formats the given number range.
---@param min number The minimum of the range.
---@param max number The maximum of the range.
---@return string # The formatted range as string.
local function toNumberRange(min, max)
local lang = mw.language.getContentLanguage()
if min == max then
return lang:formatNum(min)
end
return lang:formatNum(min) .. "–" .. lang:formatNum(max)
end
---Calculates the length of a table.
---@param t table
---@return number
local function getTableLength(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
---Creates a deep copy of a table.
---@param t table
---@return table
local function copyTable(t)
local copy = {}
for k, v in pairs(t) do
if type(v) == "table" then
copy[k] = copyTable(v)
else
copy[k] = v
end
end
return copy
end
---Creates an Wikitext link with an image.
---@param title string The title of the link.
---@param imageAttributes table The html attributes for the image.
---@return string # Wikitext link with the image.
local function createWikitextImage(title, imageAttributes)
local img = mw.html.create("img")
:attr(imageAttributes)
img = tostring(img):gsub("<img(.-) */>", "<img%1>")
return string.format('[[%s|%s]]', title, img)
end
---Finds locations, sources and `LootData` for a given item ID.
---{ locationId = { sourceId = { LootData1, LootData2, ... }, ... }, ... }
---@param itemId string|number The ID of the item to search for.
---@return table<string, table<string, LootData[]>>|nil # A table containing locations, sources and `LootData` for the item, or nil if not found.
local function getSources(itemId)
itemId = tostring(itemId)
local sources = {}
for locationId, location in pairs(lootData) do
for sourceId, source in pairs(location) do
for _, drop in ipairs(source) do
if tostring(drop.id) == itemId then
sources[locationId] = sources[locationId] or {}
sources[locationId][sourceId] = sources[locationId][sourceId] or {}
table.insert(sources[locationId][sourceId], drop)
end
end
end
end
return sources
end
---Gets monster or location object based on the source ID and optionally location ID for locations and override monster.
---@param sourceId string|number The ID of the source, -1 for locations.
---@param locationId string|number|nil The ID of the location (optional), required for locations.
---@return table|nil # The stats of the monster or nil if not found.
local function getSource(sourceId, locationId)
sourceId = tostring(sourceId)
if locationId then
locationId = tostring(locationId)
--If sourceId is "-1", it means we are looking for a location
if sourceId == "-1" then
local location = locationData[locationId]
if location then
return copyTable(location)
end
end
local baseMonster = monstersStatsData[sourceId]
for _, monster in pairs(baseMonster.overrides or {}) do
for _, location in ipairs(monster.locations or {}) do
if location and tostring(location.id) == locationId then
return copyTable(monster)
end
end
end
end
--Tries to get the stats of base monster if not a location or no override is found
-- e.g. no locationId or was given but no ovverides for the monster
return copyTable(monstersStatsData[sourceId])
end
---Converts location-source-loot table to name-sourceStats-loot table.
---@param sources table<string, table<string, LootData[]>> # The drop sources for the item.
---@return table<string, table> # A table containing the sources and their loot data and and optional Invalid IDs table.
--[[
return {
["Source Name"] = {
_loot = { LootData1, LootData2, ... },
_id = sourceId,
`unpack key-value pairs from Module monstersStatsData or locationData for the source`
},
...,
["Invalid ID"] = { invalidSourceId1, invalidSourceId2, ... }
}
]]
local function getSourceList(sources)
local sourceList = {}
for locationId, sources_ in pairs(sources) do
for sourceId, loot in pairs(sources_) do
local source = getSource(sourceId, locationId)
if source then
local name = source.name
sourceList[name] = sourceList[name] or source
sourceList[name]._loot = sourceList[name]._loot or loot
sourceList[name]._id = sourceList[name]._id or sourceId
else
sourceList["Invalid ID"] = sourceList["Invalid ID"] or {}
table.insert(sourceList["Invalid ID"], sourceId)
end
end
end
return sourceList
end
---Creates a mw.html TR element containing monster and loot data.
---@param name string The name of the monster or location.
---@param src string The source of the image.
---@param loot LootData The loot data for the item.
---@param imageWidth string|number The width of the image.
---@param imageHeight string|number The height of the image.
---@return mw.html|table # mw.html TR element containing monster and loot data.
local function createMonsterLootRow(name, src, loot, imageWidth, imageHeight)
local imageHtml = mw.html.create("td")
:wikitext(createWikitextImage(
name,
{
src = "https://www.play.idlescape.com" .. src,
alt = name,
width = imageWidth,
height = imageHeight
}
))
local itemHtml = mw.html.create("td")
:wikitext('[[' .. name .. ']]')
local quantityHtml = mw.html.create("td")
:css("text-align", "center")
:wikitext(toNumberRange(loot.minAmount, loot.maxAmount))
local chanceHtml = mw.html.create("td")
:css("text-align", "center")
:wikitext(toFixed(loot.chance * 100, 3) .. "%")
local leagueHtml = mw.html.create("td")
:css("text-align", "center")
--TODO: Add indicator if league is inactive, the field is present in
-- leagueList.ts / Module:Leagues/data but some are incorrect
if loot.allowedLeagues then
for _, leagueId in ipairs(loot.allowedLeagues) do
local league = leaguesData[tostring(leagueId)]
if league then
leagueHtml:wikitext(createWikitextImage(
league.name,
{
src = "https://www.play.idlescape.com" .. league.icon,
alt = league.name,
width = 30,
height = 30
}
))
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(leagueHtml)
if loot.allowedLeagues then
rowHtml:css("background-color", "rgba(0, 0, 0, 0.15)")
end
return rowHtml
end
---Creates a table of drop sources for a given item.
---@param sources table<string, table<string, LootData[]>> # The drop sources for the item.
---@param imageWidth string|number # The width of the images in the table.
---@param imageHeight string|number # The height of the images in the table.
local function createDropSourceTable(sources, imageWidth, imageHeight)
---@param names string[] Array of wikitext.
---@return mw.html|table # 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(mw.html.create("th")
:addClass("headerSort")
:attr("tabindex", 0)
:attr("title", "Sort ascending")
:wikitext(name)
)
end
return tr
end
---Creates a mw.html SPAN element with optional text, attributes, CSS and class.
---@param text string|nil The text to be added to the span.
---@param attr table|nil The attributes to be added to the span.
---@param css table|nil The CSS styles to be added to the span.
---@param class string|nil The classes to be added to the span.
---@return table|mw.html # mw.html SPAN element.
local function createSpan(text, attr, css, class)
local span = mw.html.create("span")
if attr then span:attr(attr) end
if css then span:css(css) end
if class then span:addClass(class) end
if text then span:wikitext(text) end
return span
end
local function createCustomCollapsible(collapsed, className)
return mw.html.create("span")
:attr("id", "mw-customcollapsible-" .. className)
:css("font-weight", "700")
:addClass("mw-collapsible " .. (collapsed and " mw-collapsed" or ""))
:wikitext("[")
:node(createSpan(
collapsed and "Collapse" or "Expand",
{ ["role"] = "button" },
{
--["cursor"] = "pointer",
--["font-weight"] = "700",
["color"] = "#8cabe6"
},
"mw-customtoggle-" .. className
))
:wikitext("]")
end
local tableHtml = mw.html.create("table")
:addClass("wikitable sortable mw-collapsible")
--[[ Adding caption and collapsing the table did sift the content after the table under infobox
(normally it would occupy the space next to it), so captions are disabled for now
:tag("caption")
:wikitext("Monsters and locations")
:done()
]]
:node(createThs({ " ", "Source", "Quantity", "Rarity", "Leagues" }))
for name, source in pairs(getSourceList(sources)) do
local rows = {}
if name == "Invalid ID" then
table.insert(
rows,
mw.html.create("tr")
:tag("td")
:attr("colspan", 5)
:wikitext("No sources with IDs: " .. table.concat(source, ", "))
:done()
)
else
local _loot = source._loot
local multiDrop = getTableLength(_loot) > 1
local relatedKey = itemsData[tostring(source.relatedKey)]
local relatedKeyImage = relatedKey and relatedKey.itemImage
local src = source.imageOverride or relatedKeyImage or (monstersData[tostring(source._id)] or {}).image
src = src and src:sub(1, 1) ~= "/" and "/" .. src or src
local className = name:gsub(" ", "_"):gsub("[^%w_]", "")
if multiDrop then
table.insert(
rows,
mw.html.create("tr")
:tag("td")
:wikitext(createWikitextImage(
name,
{
src = "https://www.play.idlescape.com" .. src,
alt = name,
width = imageWidth,
height = imageHeight
}
))
:done()
:tag("td")
:wikitext("[[" .. name .. "]]")
:done()
:tag("td")
:attr("colspan", 3)
:css("text-align", "center")
:addClass("mw-customtoggle-" .. className)
:node(createCustomCollapsible(
false,
className
))
:node(createCustomCollapsible(
true,
className
))
:node(createSpan(
"▼",
{ ["id"] = "mw-customcollapsible-" .. className },
{ ["float"] = "right" },
"mw-collapsible"
))
:node(createSpan(
"▲",
{ ["id"] = "mw-customcollapsible-" .. className },
{ ["float"] = "right" },
"mw-collapsible mw-collapsed"
))
:done()
)
end
for _, loot in ipairs(_loot) do
local row = createMonsterLootRow(name, src, loot, imageWidth, imageHeight)
if multiDrop then
row
:addClass("mw-collapsible mw-collapsed")
:attr("id", "mw-customcollapsible-" .. className)
end
table.insert(rows, row)
end
end
for _, row in ipairs(rows) do
tableHtml:node(row)
end
end
return tableHtml
end
function p.dropSources(frame)
local args = frame:getParent().args or {}
return p._dropSources(args)
end
function p._dropSources(args)
local name = args["name"] or args["title"] or mw.title.getCurrentTitle().text
local itemId = findId._findId({ name, "item" })
if itemId == "id not found" then
return "No id found for item '" .. name .. "'."
end
local sources = getSources(itemId)
if not sources then
return "No sources found for '" .. name .. "', Modules: Loot/data or Monsters_stats/data may be outdated."
end
local html = createDropSourceTable(sources, args["width"] or 30, args["height"] or 30)
return html
end
return p