Difference between revisions of "Module:Infobox Item"
Jump to navigation
Jump to search
(Ignore obsolete set enchantments) |
Demcookies (talk | contribs) m (Fix divide farming Harvest Time by 60 to show time in minutes) |
||
| (24 intermediate revisions by 3 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
| − | local | + | local module_names = { |
| − | + | item = 'Module:Items/data', | |
| − | + | enchantment = 'Module:Enchantment/data', | |
| − | + | location = 'Module:Location/data', | |
| − | + | craftaug = 'Module:CraftingAugmenting/data', | |
| − | + | farming = 'Module:Farming/data', | |
| + | farmloot = 'Module:Farming loot/data', | ||
| + | augloot = 'Module:Augmenting loot/data', | ||
| + | cooking = 'Module:CookingList/data', | ||
| + | ability = 'Module:Abilities/data', | ||
| + | gameshop = 'Module:GameShopItems/data', | ||
| + | itemsets = 'Module:Item sets', | ||
} | } | ||
| − | local | + | local loaded_modules = {} |
local headerCount = 1 | local headerCount = 1 | ||
local labelCount = 1 | local labelCount = 1 | ||
local dataCount = 1 | local dataCount = 1 | ||
| + | |||
| + | local cups = { | ||
| + | 16000,16001,16002,16003,16004,16005,16006,16007, | ||
| + | 16008,16009,16010,16011,16012,16013,16014,16015 | ||
| + | } | ||
| + | |||
| + | ---Checks if a table contains a specific value. | ||
| + | ---@param t table | ||
| + | ---@param value any | ||
| + | ---@return boolean | ||
| + | local function hasValue(t, value) | ||
| + | for _, v in pairs(t) do | ||
| + | if value == v then | ||
| + | return true | ||
| + | end | ||
| + | end | ||
| + | return false | ||
| + | end | ||
| + | |||
| + | ---Creates a deep copy of a tables and functions. | ||
| + | ---Does not work with functions with upvalues and maybe userdata. | ||
| + | ---@generic T | ||
| + | ---@param t T The value to copy. | ||
| + | ---@return T # A new value that is a deep copy of the original. | ||
| + | local function copyTable(t) | ||
| + | if type(t) ~= "table" then | ||
| + | return t | ||
| + | elseif type(t) == "function" then | ||
| + | return loadstring(string.dump(t)) | ||
| + | end | ||
| + | local copy = {} | ||
| + | for k, v in pairs(t) do | ||
| + | copy[k] = copyTable(v) | ||
| + | end | ||
| + | return copy | ||
| + | end | ||
| + | |||
| + | ---Formats a number up to a given number of decimal places, removing trailing zeros. | ||
| + | ---@param num number|string The number to format. | ||
| + | ---@param digits number The number of decimal places to include (must be a non-negative integer). | ||
| + | ---@return string|nil # The formatted number as a string or nil if the input is not a valid number. | ||
| + | local function toFixed(num, digits) | ||
| + | local n = tonumber(num) | ||
| + | if not n then | ||
| + | return nil | ||
| + | end | ||
| + | digits = math.max(0, math.floor(digits)) | ||
| + | local formatted = string.format("%." .. digits .. "f", n):gsub("%.?0+$", "") | ||
| + | return formatted | ||
| + | end | ||
local function h() | local function h() | ||
| − | + | local s = "header" .. headerCount | |
| − | + | headerCount = headerCount + 1 | |
| − | + | labelCount = headerCount | |
| − | + | dataCount = headerCount | |
| − | + | return s | |
end | end | ||
local function sbreak() | local function sbreak() | ||
| − | + | local s = "sbreak" .. headerCount | |
| − | + | headerCount = headerCount + 1 | |
| − | + | labelCount = headerCount | |
| − | + | dataCount = headerCount | |
| − | + | return s | |
end | end | ||
local function l() | local function l() | ||
| − | + | local s = "label" .. labelCount | |
| − | + | dataCount = labelCount | |
| − | + | labelCount = labelCount + 1 | |
| − | + | headerCount = labelCount | |
| − | + | return s | |
end | end | ||
| − | local function d() | + | local function d() |
| − | + | local s = "data" .. dataCount | |
| − | + | dataCount = dataCount + 1 | |
| − | + | headerCount = dataCount | |
| − | + | labelCount = dataCount | |
| − | + | return s | |
end | end | ||
local function sl() | local function sl() | ||
| − | + | local s = "s" .. l() | |
| − | + | return s | |
end | end | ||
local function sd() | local function sd() | ||
| − | + | local s = "s" .. d() | |
| − | + | return s | |
end | end | ||
| − | function p.loadData(data_type) | + | ---@param data_type string |
| − | + | ---@return table | |
| − | + | function p.loadData(data_type) | |
| − | + | local module_name = module_names[data_type] | |
| − | + | if loaded_modules[module_name] == nil then | |
| − | + | local status, module = pcall(mw.loadData, module_name) | |
| − | + | if not status then | |
| + | status, module = pcall(require, module_name) | ||
| + | end | ||
| + | if status then | ||
| + | loaded_modules[module_name] = module | ||
| + | else | ||
| + | error("Failed to load module: " .. data_type .. " / " .. tostring(module_name) .. "\n" .. module) | ||
| + | end | ||
| + | end | ||
| + | |||
| + | return loaded_modules[module_name] | ||
end | end | ||
| − | local function | + | local function getItem(id) |
| − | + | return p.loadData("item")[tostring(id)] | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
end | end | ||
| − | local function | + | local function getEnchant(id) |
| − | + | return p.loadData("enchantment")[tostring(id)] | |
end | end | ||
| − | local function | + | local function getEnchantName(id) |
| − | + | local enchantment = getEnchant(id) | |
| + | return enchantment and enchantment.name | ||
end | end | ||
| − | local function | + | local function getModuleItemSet() |
| − | + | return p.loadData("itemsets") | |
end | end | ||
local function getItemName(id) | local function getItemName(id) | ||
| − | + | return p.loadData("item")[tostring(id)].name | |
end | end | ||
local function getCraftAug(id) | local function getCraftAug(id) | ||
| − | + | return p.loadData("craftaug")[tostring(id)] | |
| + | end | ||
| + | |||
| + | ---@param id string|number | ||
| + | ---@return CookingItem|nil | ||
| + | local function getCookingIngredient(id) | ||
| + | return p.loadData("cooking")[tostring(id)] | ||
| + | end | ||
| + | |||
| + | ---@param id string|number|nil # Returns all if nil | ||
| + | ---@return AugmentingLoot|table<string, AugmentingLoot>|nil | ||
| + | local function getAugLoot(id) | ||
| + | local augloot = p.loadData("augloot") | ||
| + | return id == nil and augloot or augloot[tostring(id)] | ||
| + | end | ||
| + | |||
| + | ---@param id string|number | ||
| + | ---@return Ability|nil | ||
| + | local function getAbility(id) | ||
| + | return p.loadData("ability")[tostring(id)] | ||
| + | end | ||
| + | |||
| + | ---@param id string|number | ||
| + | ---@return FarmingLoot[]|nil | ||
| + | local function getYield(id) | ||
| + | return p.loadData("farmloot")[tostring(id)] | ||
| + | end | ||
| + | |||
| + | ---@param id string|number|nil | ||
| + | ---@return FarmingStats|FarmingStats[]|nil # farming stats for the given id or for all seeds if id is nil | ||
| + | local function getFamingStats(id) | ||
| + | local data = p.loadData("farming") | ||
| + | return id == nil and data or data[tostring(id)] | ||
| + | end | ||
| + | |||
| + | ---@param itemId string|number # Item ID, is not the same as the `id` in the shop. | ||
| + | ---@return nil | ||
| + | local function getGeneralShopItem(itemId) | ||
| + | local id = tonumber(itemId) | ||
| + | if not id then return nil end | ||
| + | |||
| + | local items = p.loadData("gameshop") | ||
| + | for _, item in pairs(items) do | ||
| + | if item.itemID == id then | ||
| + | return item | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | ---@return Item|nil | ||
| + | local function findItem(name) | ||
| + | local lname = name:lower() | ||
| + | |||
| + | --Remove leading and trailing spaces. | ||
| + | lname = lname:gsub('^%s*(.-)%s*$', '%1') | ||
| + | for key, item in pairs(p.loadData("item")) do | ||
| + | if lname == item.name:lower() then | ||
| + | return item | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | local function dottedTooltip(name, tooltip) | ||
| + | return string.format( | ||
| + | '<span class="rt-commentedText tooltip tooltip-dotted" title="%s">%s</span>', | ||
| + | tooltip, | ||
| + | name | ||
| + | ) | ||
| + | end | ||
| + | |||
| + | ---Formats the given chance as a string presentation of percentage or 10k/100k fraction. | ||
| + | ---@param chance number The chance to format. 1 equals 100%. | ||
| + | ---@return string # The formatted chance as a string. | ||
| + | local function formatChance(chance) | ||
| + | local sign = chance < 0 and "-" or "" | ||
| + | local abs = math.abs(chance) | ||
| + | |||
| + | if abs < 0.001 then | ||
| + | return sign .. toFixed(abs * 10000, 1) .. "/10k" | ||
| + | elseif abs < 0.0001 then | ||
| + | return sign .. toFixed(abs * 100000, 1) .. "/100k" | ||
| + | end | ||
| + | |||
| + | return toFixed(chance * 100, 3) .. "%" | ||
end | end | ||
local function fullUrl(url) | local function fullUrl(url) | ||
| − | + | local newUrl = url | |
| − | + | if url:sub(1,5) == "https" then | |
| − | + | return newUrl | |
| − | + | end | |
| − | + | ||
| − | + | if url:sub(1,1) ~= "/" then | |
| − | + | newUrl = "/" .. newUrl | |
| − | + | end | |
| − | + | ||
| − | + | newUrl = "https://www.play.idlescape.com" .. newUrl | |
| − | + | return newUrl | |
end | end | ||
| − | local function icon(name, url, word) | + | local function icon(name, url, word, size) |
| − | + | size = size or 20 | |
| − | + | url = fullUrl(url) | |
| − | + | return string.format( | |
| − | + | '[[%s|<img src="%s" alt="%s" width="%s">%s]]', | |
| − | + | name, | |
| − | + | url, | |
| − | + | name, | |
| − | + | size, | |
| + | word and name or "" | ||
| + | ) | ||
end | end | ||
| + | ---@param id string|number # Item ID. | ||
| + | ---@param word boolean # If true, show the name of the item after the icon. | ||
| + | ---@return string # The icon wikitext. | ||
local function itemImage(id, word) | local function itemImage(id, word) | ||
| − | + | local item = p.loadData("item")[tostring(id)] | |
| − | + | if not item then return "" end | |
| − | + | local url = "" | |
| − | + | if item.itemIcon then | |
| − | + | url = item.itemIcon | |
| − | + | else | |
| − | + | url = item.itemImage | |
| − | + | end | |
| + | return icon(item.name, url, word) | ||
end | end | ||
| − | local function | + | ---Creates an Wikitext link with an image inside a div. |
| − | + | ---@param title string # The title of the link. | |
| − | + | ---@param imageAttributes table # The html attributes for the image. | |
| − | + | ---@param divCSS table # The CSS styles for the div. | |
| − | + | ---@return string # The Wikitext link with the image. | |
| − | + | local function createWikitextImage(title, imageAttributes, divCSS) | |
| − | + | divCSS = divCSS or {} | |
| − | + | divCSS.display = divCSS.display or "inline-block" --Makes the div same size as the image | |
| − | + | local e = mw.html.create("div") | |
| + | :css(divCSS) | ||
| + | :tag("img") | ||
| + | :attr(imageAttributes) | ||
| + | :done() | ||
| + | e = tostring(e):gsub("<img(.-) */>", "<img%1>") | ||
| + | return string.format('[[%s|%s]]', title, e) | ||
end | end | ||
| − | local function | + | ---Creates a Wikitext containing links and images for the abilities. |
| − | + | ---@param abilities number[]|{id: number}[]|nil Array of ability IDs. | |
| − | + | ---@param unique boolean|nil Skips duplicate abilities if truthy, otherwise creates an element for each ability in order. | |
| − | + | ---@return string # The Wikitext containing links and images for the abilities. | |
| − | + | local function createAbilityRotation(abilities, unique) | |
| − | + | if not abilities then return "" end | |
| − | + | local colors = { | |
| − | + | Melee = "red", | |
| + | Range = "green", | ||
| + | Magic = "blue", | ||
| + | } | ||
| + | |||
| + | local seenIds = {} | ||
| + | local images = {} | ||
| + | for _, id in ipairs(abilities) do | ||
| + | if type(id) == "table" then | ||
| + | id = id.id | ||
| + | end | ||
| + | if not seenIds[id] then | ||
| + | seenIds[id] = unique and true or false | ||
| + | local ability = getAbility(id) | ||
| + | if ability then | ||
| + | -- Check if the ability page exists and add tooltip if not | ||
| + | local exists = mw.title.new(ability.abilityName) | ||
| + | exists = exists and exists.exists | ||
| + | local attributes = { | ||
| + | src = fullUrl(ability.abilityImage), | ||
| + | alt = ability.abilityName .. ". " .. ability.description, | ||
| + | width = 30, | ||
| + | class = not exists and "tooltip" or nil, | ||
| + | title = not exists and ability.abilityName .. ". " .. ability.description or nil, | ||
| + | } | ||
| + | local css = { | ||
| + | ["box-shadow"] = "0 0 2px 1px " .. (colors[ability.damageType] or "white"), | ||
| + | background = colors[ability.damageType] or "white", | ||
| + | margin = "5px" | ||
| + | } | ||
| + | table.insert(images, createWikitextImage(ability.abilityName, attributes, css)) | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | return table.concat(images, "") | ||
end | end | ||
| − | local function | + | local function locationImage(id, word) |
| − | + | local loc = p.loadData("location")[tostring(id)] | |
| + | local url = "" | ||
| + | if loc.locationImage then | ||
| + | url = loc.locationImage | ||
| + | else | ||
| + | return "[[" .. loc.name .. "]]" | ||
| + | end | ||
| + | return icon(loc.name, url, word) | ||
end | end | ||
local function gatheringSource(id) | local function gatheringSource(id) | ||
| − | + | local s = "" | |
| − | + | for key, loc in pairs(p.loadData('location')) do | |
| − | + | if loc.loot then | |
| − | + | for key2, loot in pairs(loc.loot) do | |
| − | + | if id == loot.id then | |
| − | + | s = s .. locationImage(loc.locID, true) | |
| − | + | s = s .. "<br>" | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
| − | + | return s | |
end | end | ||
| − | + | local function farmingSource(id) | |
| − | + | local s = "" | |
| − | + | for key, item in pairs(p.loadData('item')) do | |
| − | + | if item.farmingStats then | |
| − | + | for key2, yield in pairs(item.farmingStats.yield) do | |
| − | + | if id == yield.id then | |
| − | + | s = s .. itemImage(item.id, true) | |
| − | + | s = s .. "<br>" | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
| − | + | return s | |
| − | + | end | |
| − | |||
local function smithingSource(id) | local function smithingSource(id) | ||
| − | + | local s = "" | |
| − | + | local item = getItem(id) | |
| − | + | if item.skill == "smithing" and item.name ~= 'Ichor' then | |
| − | + | s = '[[Smithing]]<br>' | |
| − | + | end | |
| − | + | return s | |
end | end | ||
local function cookingSource(id) | local function cookingSource(id) | ||
| − | + | local s = "" | |
| − | + | local item = getItem(id) | |
| − | + | if (item.class == "cooking-ingredient" and not item.ingredientTags) or item.class == 'cookedFish' or item.name=='Ashes' then | |
| − | + | s = '[[Cooking]]<br>' | |
| − | + | end | |
| − | + | return s | |
end | end | ||
local function runecraftingSource(id) | local function runecraftingSource(id) | ||
| − | + | local s = "" | |
| − | + | local item = getItem(id) | |
| − | + | if item.class == "cloth" or (item.class == 'rune' and item.requiredResources) then | |
| − | + | s = '[[Runecrafting]]<br>' | |
| − | + | end | |
| − | + | return s | |
end | end | ||
local function scrollcraftingSource(id) | local function scrollcraftingSource(id) | ||
| − | + | local s = "" | |
| − | + | local item = getItem(id) | |
| − | + | if item.class == "enchanted-scroll" and item.level and item.level < 100 then | |
| − | + | s = '[[Scrollcrafting]]<br>' | |
| − | + | end | |
| − | + | return s | |
end | end | ||
| − | |||
local function craftingSource(id) | local function craftingSource(id) | ||
| − | + | local s = "" | |
| − | + | local item = getItem(id) | |
| − | + | if item.craftable then | |
| − | + | s = '[[Crafting]]<br>' | |
| − | + | end | |
| − | + | return s | |
end | end | ||
local function findSource(id) | local function findSource(id) | ||
| − | + | local s = "" | |
| − | + | s = s .. gatheringSource(id) | |
| − | + | -- s = s .. farmingSource(id) | |
| − | + | s = s .. scrollcraftingSource(id) | |
| − | + | s = s .. runecraftingSource(id) | |
| − | + | s = s .. smithingSource(id) | |
| − | + | s = s .. craftingSource(id) | |
| − | + | s = s .. cookingSource(id) | |
| − | + | s = s:gsub("<br>$", "") | |
| − | + | return s | |
| − | + | end | |
| − | + | ||
| + | ---@param item Item | ||
| + | ---@return integer | ||
| + | local function getItemTier(item) | ||
| + | local maxRequiredLevel | ||
| + | if item.requiredLevel then | ||
| + | for _, level in pairs(item.requiredLevel) do | ||
| + | if maxRequiredLevel == nil or maxRequiredLevel < level then | ||
| + | maxRequiredLevel = level | ||
| + | end | ||
| + | end | ||
| + | maxRequiredLevel = math.floor(math.floor(maxRequiredLevel / 10)) | ||
| + | end | ||
| + | return item.overrideItemTier or maxRequiredLevel or item.enchantmentTier or 1 | ||
| + | end | ||
| + | |||
| + | local function getDustId(tier) | ||
| + | local AFFIX_DUST_PER_ITEM_TIER = { | ||
| + | 550, | ||
| + | 550, | ||
| + | 550, | ||
| + | 551, | ||
| + | 552, | ||
| + | 553, | ||
| + | 554, | ||
| + | 554, | ||
| + | 554, | ||
| + | } | ||
| + | tier = (tonumber(tier) or 0) + 1 -- +1 for lua indexing | ||
| + | tier = math.floor(tier) | ||
| + | tier = math.min(math.max(tier, 1), 9) | ||
| + | return AFFIX_DUST_PER_ITEM_TIER[tier] | ||
| + | end | ||
| + | |||
| + | ---@param rarity string|nil | ||
| + | ---@return integer | ||
| + | local function getScrapId(rarity) | ||
| + | rarity = rarity or "common" | ||
| + | local scrapIds = { | ||
| + | common = 555, | ||
| + | uncommon = 556, | ||
| + | rare = 557, | ||
| + | epic = 558, | ||
| + | legendary = 559 | ||
| + | } | ||
| + | return scrapIds[rarity] | ||
| + | end | ||
| + | |||
| + | ---@param args table | ||
| + | ---@param title string | ||
| + | ---@param id string|number|false|nil # Primary loot id, skipped if falsy | ||
| + | ---@param scrapping table|nil # data.chance, data.itemID (AugmentingLoot scrappingSuccess or scrappingFail) | ||
| + | local function addScrapping(args, title, id, scrapping) | ||
| + | local text = id and (itemImage(id, true) .. "<br>") or "" | ||
| + | if scrapping then | ||
| + | if scrapping.chance and scrapping.chance ~= 1 then | ||
| + | text = text .. formatChance(scrapping.chance) .. " " | ||
| + | end | ||
| + | if scrapping.minimum and scrapping.maximum then | ||
| + | text = text .. scrapping.minimum .. "–" .. scrapping.maximum .. " " | ||
| + | end | ||
| + | text = text .. itemImage(scrapping.itemID, true) | ||
| + | end | ||
| + | args[sl()] = title | ||
| + | args[sd()] = text | ||
| + | end | ||
| + | |||
| + | ---@param args table # infobox args table | ||
| + | ---@param augTransform boolean # true for augmenting, false for researching | ||
| + | ---@param transformsFrom table<number, ItemTransform>|nil # table of source ID as key, and list of transforms as value | ||
| + | ---@param transformsTo ItemTransform[]|nil # list of transforms | ||
| + | local function addTransforms(args, augTransform, transformsFrom, transformsTo) | ||
| + | if not (transformsFrom or transformsTo) then return end | ||
| + | |||
| + | ---@param transform ItemTransform | ||
| + | ---@param id number|false|nil # uses this for item's image instead of transform.newItemID | ||
| + | local function createTransform(transform, id) | ||
| + | if (transform.augmentingTransform or false) == augTransform then | ||
| + | local image = itemImage(id or transform.newItemID, true) | ||
| + | if transform.augmentingTransform then | ||
| + | return image .. " at level " .. (transform.augmentationLevel or 1) | ||
| + | else | ||
| + | return formatChance(transform.chance) .. " " .. image | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | ---@param transforms table<number, ItemTransform> | ||
| + | ---@param isKeyId boolean|nil # wether the table keys are item IDs or indicies | ||
| + | local function createTransformList(transforms, isKeyId) | ||
| + | local list = {} | ||
| + | for i, transform in pairs(transforms) do | ||
| + | local text = createTransform(transform, isKeyId and i) | ||
| + | table.insert(list, text) | ||
| + | end | ||
| + | return list | ||
| + | end | ||
| + | |||
| + | args[sbreak()] = "yes" | ||
| + | |||
| + | local list | ||
| + | if transformsFrom then | ||
| + | list = createTransformList(transformsFrom, true) | ||
| + | args[sl()] = "Transforms from" | ||
| + | args[sd()] = table.concat(list, "<br>") | ||
| + | end | ||
| + | if transformsTo then | ||
| + | list = createTransformList(transformsTo) | ||
| + | args[sl()] = "Transforms to" | ||
| + | args[sd()] = table.concat(list, "<br>") | ||
| + | end | ||
| + | end | ||
| + | |||
| + | ---@param args table | ||
| + | ---@param item Item | ||
| + | ---@param transformsTo ItemTransform[]|nil # list of transforms | ||
| + | ---@param transformsFrom table<number, ItemTransform>|nil # list of transforms | ||
| + | ---@return boolean | ||
| + | local function addResearch(args, item, transformsTo, transformsFrom) | ||
| + | local isCup = hasValue(cups, item.id) | ||
| + | local canResearch = not getGeneralShopItem(item.id) or isCup | ||
| + | local craftAug = getCraftAug(item.id) | ||
| + | |||
| + | if craftAug and canResearch then | ||
| + | local augLoot = getAugLoot(tostring(item.id)) | ||
| + | local tier = getItemTier(item) | ||
| + | local dustId = not item.noDustFromResearching and getDustId(tier) or nil | ||
| + | |||
| + | if not item.noScrapFromResearching then | ||
| + | local success = augLoot and augLoot.scrappingSuccess or nil | ||
| + | addScrapping(args, "Success", dustId, success) | ||
| + | end | ||
| + | |||
| + | -- General Shop items are hardcoded to never fail (CUPS atm) | ||
| + | if not isCup then | ||
| + | local scrapId = item.researchesIntoDust and dustId or getScrapId(item.rarity) | ||
| + | local fail = augLoot and augLoot.scrappingFail or nil | ||
| + | addScrapping(args, "Fail", scrapId, fail) | ||
| + | end | ||
| + | end | ||
| + | |||
| + | addTransforms(args, false, transformsTo, transformsFrom) | ||
| + | |||
| + | return craftAug and canResearch or transformsFrom ~= nil | ||
| + | end | ||
| + | |||
| + | ---@param prefix 'offensiveDamageAffinity'|'offensiveAccuracyAffinityRating'|'defensiveDamageAffinity' | ||
| + | ---@param description string|nil | ||
| + | ---@param uppercase boolean|nil | ||
| + | ---@return table<string, string> | ||
| + | local function createAffinityDictionary(prefix, description, uppercase) | ||
| + | description = description or "" | ||
| + | uppercase = uppercase or false | ||
| + | |||
| + | local allDamageTypes = { | ||
| + | "Melee", | ||
| + | "Magic", | ||
| + | "Range", | ||
| + | "Piercing", | ||
| + | "Blunt", | ||
| + | "Slashing", | ||
| + | "Chaos", | ||
| + | "Nature", | ||
| + | "Fire", | ||
| + | "Ice", | ||
| + | "Lightning", | ||
| + | "Poison", | ||
| + | "Typeless", | ||
| + | "Heal" | ||
| + | } | ||
| + | |||
| + | local affinities = {} | ||
| + | for _, damageType in ipairs(allDamageTypes) do | ||
| + | local key = string.format("%s.%s", prefix, damageType) | ||
| + | local val = string.format("%s %s", uppercase and damageType:upper() or damageType, description) | ||
| + | local affinity = val:gsub("%s+$", "") | ||
| + | affinities[key] = affinity | ||
| + | end | ||
| + | return affinities | ||
end | end | ||
| + | local function getLabelFromAugBonus(bonusName) | ||
| + | local equipmentStatsToLabelMapping = { | ||
| + | ["weaponBonus.strength"] = "Strength", | ||
| + | ["weaponBonus.intellect"] = "Intellect", | ||
| + | ["weaponBonus.dexterity"] = "Dexterity", | ||
| + | ["offensiveCritical.chance"] = "Crit Chance", | ||
| + | ["offensiveCritical.damageMultiplier"] = "Crit Mult", | ||
| + | ["armorBonus.protection"] = "Protection", | ||
| + | ["armorBonus.resistance"] = "Resistance", | ||
| + | ["armorBonus.agility"] = "Agility", | ||
| + | ["armorBonus.stamina"] = "Stamina", | ||
| + | ["defensiveCritical.chance"] = "Crit Avoidance", | ||
| + | ["defensiveCritical.damageMultiplier"] = "Crit Reduction", | ||
| + | ["toolBoost.fishing"] = "Fishing", | ||
| + | ["toolBoost.fishingBaitPower"] = "Bait Power", | ||
| + | ["toolBoost.fishingReelPower"] = "Reel Power", | ||
| + | ["toolBoost.fishingRarityPower"] = "Bonus Rarity", | ||
| + | ["toolBoost.mining"] = "Mining", | ||
| + | ["toolBoost.foraging"] = "Foraging", | ||
| + | ["toolBoost.farming"] = "Farming", | ||
| + | ["toolBoost.cooking"] = "Cooking", | ||
| + | ["toolBoost.smithing"] = "Smithing", | ||
| + | ["toolBoost.enchanting"] = "Enchanting", | ||
| + | ["toolBoost.runecrafting"] = "Runecrafting" | ||
| + | } | ||
| + | for k, v in pairs(createAffinityDictionary("offensiveDamageAffinity", "") or {}) do | ||
| + | equipmentStatsToLabelMapping[k] = v | ||
| + | end | ||
| + | for k, v in pairs(createAffinityDictionary("offensiveAccuracyAffinityRating", "Accuracy") or {}) do | ||
| + | equipmentStatsToLabelMapping[k] = v | ||
| + | end | ||
| + | for k, v in pairs(createAffinityDictionary("defensiveDamageAffinity", "") or {}) do | ||
| + | equipmentStatsToLabelMapping[k] = v | ||
| + | end | ||
| + | |||
| + | return equipmentStatsToLabelMapping[bonusName] or bonusName | ||
| + | end | ||
| + | |||
| + | ---@param item Item | ||
local function createInfobox(item) | local function createInfobox(item) | ||
| − | + | local lang = mw.language.getContentLanguage() | |
| − | + | local args = {} | |
| − | + | args.autoheaders = "y" | |
| − | + | args.subbox = "no" | |
| − | + | args.bodystyle = " " | |
| − | + | args.title = item.name | |
| − | + | ||
| − | + | local url = item.itemIcon or item.itemImage | |
| − | + | args.image = '<img src="' .. fullUrl(url) .. '" width="150">' | |
| − | + | ||
| − | + | if item.value then | |
| − | + | args[l()] = "Vendor Value" | |
| − | + | args[d()] = lang:formatNum(item.value) .. " " .. icon('Gold', "/images/gold_coin.png", false, 13) | |
| − | + | end | |
| − | + | ||
| − | + | --TODO: add this back when the market is fixed | |
| − | + | --[[if item.tradeable then | |
| − | + | args[l()] = icon('Market', "/images/ui/marketplace_icon.png") | |
| − | + | local market = require("Module:Market")["_price"]({item.name, 1, 1}) | |
| − | + | if market then | |
| − | + | args[d()] = addSeparator(market) | |
| − | + | else | |
| − | + | args[d()] = "Yes" | |
| − | + | end | |
| − | + | end]] | |
| − | + | ||
| − | + | if item.rarity then | |
| − | + | args[l()] = "Rarity" | |
| − | + | args[d()] = item.rarity:gsub("^%l", string.upper) | |
| − | + | end | |
| − | + | ||
| − | + | if item.requiredLevel then | |
| − | + | local levels = {} | |
| − | + | for skill, level in pairs(item.requiredLevel) do | |
| − | + | skill = skill:gsub("^%l", string.upper) | |
| − | + | table.insert(levels, level .. " " .. skill) | |
| − | + | end | |
| − | + | args[l()] = "Level" | |
| − | + | args[d()] = table.concat(levels, "<br>") | |
| − | + | end | |
| − | + | ||
| − | + | --TODO: findSource doesn't work so it's disabled for now. Does need a big rework | |
| − | + | --args[l()] = "Source" | |
| − | + | --args[d()] = findSource(item.id) | |
| − | + | ||
| − | + | if item.heat then | |
| − | + | args[l()] = itemImage(2, false) | |
| − | + | args[d()] = lang:formatNum(item.heat) | |
| − | + | end | |
| − | + | ||
| − | + | if item.forcedEnchant then | |
| − | + | args[l()] = "Enchantment" | |
| − | + | args[d()] = "[[" .. getEnchantName(item.forcedEnchant) .. "]]" | |
| − | + | ||
| − | + | args[l()] = "Enchantment Level" | |
| − | + | args[d()] = item.forcedEnchantAmount | |
| − | + | end | |
| − | + | ||
| − | + | if item.enchantmentTier then | |
| − | + | args[l()] = "Enchantment Slots" | |
| − | + | args[d()] = item.enchantmentTier | |
| − | + | end | |
| − | + | ||
| − | + | local stats = item.equipmentStats | |
| − | + | if stats then | |
| − | + | local slot = stats.slot and stats.slot:gsub("^%l", string.upper) | |
| − | + | args[l()] = "Slot" | |
| − | + | args[d()] = stats.slot and slot | |
| − | + | ||
| − | + | if stats.oneHanded == false then | |
| − | + | args[l()] = "Two-handed" | |
| − | + | args[d()] = "Yes" | |
| − | + | end | |
| − | + | ||
| − | + | if stats.attackSpeed then | |
| − | + | args[l()] = "Attack Speed" | |
| − | + | args[d()] = stats.attackSpeed .. "s" | |
| − | + | end | |
| − | + | ||
| − | + | if stats.toolBoost then | |
| − | + | args[h()] = "Tool Stats" | |
| − | + | ||
| − | + | for _, stat in ipairs(stats.toolBoost) do | |
| − | + | if stat.boost ~= 0 then | |
| − | + | local sign = stat.boost > 0 and "+" or "" | |
| − | + | local boost = sign .. stat.boost | |
| − | + | ||
| − | + | args[l()] = getLabelFromAugBonus("toolBoost." .. stat.skill) | |
| − | + | args[d()] = boost | |
| − | + | end | |
| − | + | end | |
| − | + | end | |
| − | + | ||
| − | + | args[h()] = "Offensive Stats" | |
| − | + | ||
| − | + | local stat = stats.offensiveCritical | |
| − | + | if stat then | |
| − | + | args[l()] = "Crit Chance" | |
| − | + | args[d()] = toFixed(stat.chance * 100, 3) .. "%" | |
| − | + | args[l()] = "Crit Multiplier" | |
| − | + | args[d()] = stat.damageMultiplier .. "x" | |
| − | + | end | |
| − | + | ||
| − | + | stat = stats.weaponBonus | |
| − | + | if stat then | |
| − | + | args[sl()] = "Str" | |
| − | + | args[sd()] = stat.strength | |
| − | + | args[sl()] = "Int" | |
| − | + | args[sd()] = stat.intellect | |
| − | + | args[sl()] = "Dex" | |
| − | + | args[sd()] = stat.dexterity | |
| − | + | end | |
| − | + | ||
| − | + | args[h()] = "Offensive Affinity" | |
| − | + | stat = stats.offensiveDamageAffinity | |
| − | + | if stat then | |
| − | + | args[sl()] = "Melee" | |
| − | + | args[sd()] = stat.Melee and toFixed(stat.Melee * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Magic" | |
| − | + | args[sd()] = stat.Magic and toFixed(stat.Magic * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Range" | |
| − | + | args[sd()] = stat.Range and toFixed(stat.Range * 100 - 100, 3) .. "%" | |
| − | + | args[sbreak()] = "yes" | |
| − | + | ||
| − | + | args[sl()] = "Piercing" | |
| − | + | args[sd()] = stat.Piercing and toFixed(stat.Piercing * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Blunt" | |
| − | + | args[sd()] = stat.Blunt and toFixed(stat.Blunt * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Slashing" | |
| − | + | args[sd()] = stat.Slashing and toFixed(stat.Slashing * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Fire" | |
| − | + | args[sd()] = stat.Fire and toFixed(stat.Fire * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Ice" | |
| − | + | args[sd()] = stat.Ice and toFixed(stat.Ice * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Nature" | |
| − | + | args[sd()] = stat.Nature and toFixed(stat.Nature * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Chaos" | |
| − | + | args[sd()] = stat.Chaos and toFixed(stat.Chaos * 100 - 100, 3) .. "%" | |
| − | + | args[sbreak()] = "yes" | |
| − | + | end | |
| − | + | ||
| − | + | args[h()] = "Accuracy" | |
| − | + | stat = stats.offensiveAccuracyAffinityRating | |
| − | + | if stat then | |
| − | + | args[sl()] = "Melee" | |
| − | + | args[sd()] = stat.Melee | |
| − | + | args[sl()] = "Magic" | |
| − | + | args[sd()] = stat.Magic | |
| − | + | args[sl()] = "Range" | |
| − | + | args[sd()] = stat.Range | |
| − | + | args[sbreak()] = "yes" | |
| − | + | ||
| − | + | args[sl()] = "Piercing" | |
| − | + | args[sd()] = stat.Piercing | |
| − | + | args[sl()] = "Blunt" | |
| − | + | args[sd()] = stat.Blunt | |
| − | + | args[sl()] = "Slashing" | |
| − | + | args[sd()] = stat.Slashing | |
| − | + | args[sl()] = "Fire" | |
| − | + | args[sd()] = stat.Fire | |
| − | + | args[sl()] = "Ice" | |
| − | + | args[sd()] = stat.Ice | |
| − | + | args[sl()] = "Nature" | |
| − | + | args[sd()] = stat.Nature | |
| − | + | args[sl()] = "Chaos" | |
| − | + | args[sd()] = stat.Chaos | |
| − | + | args[sbreak()] = "yes" | |
| − | + | end | |
| − | + | ||
| − | + | args[h()] = "Defensive Stats" | |
| − | + | ||
| − | + | stat = stats.defensiveCritical | |
| − | + | if stat then | |
| − | + | args[l()] = "Crit Avoidance" | |
| − | + | args[d()] = toFixed(stat.chance * 100, 3) .. "%" | |
| − | + | args[l()] = "Crit Reduction" | |
| − | + | args[d()] = stat.damageMultiplier | |
| − | + | end | |
| − | + | ||
| − | + | stat = stats.armorBonus | |
| − | + | if stat then | |
| − | + | args[sl()] = "Protection" | |
| − | + | args[sd()] = stat.protection | |
| − | + | args[sl()] = "Resistance" | |
| − | + | args[sd()] = stat.resistance | |
| − | + | args[sl()] = "Agility" | |
| − | + | args[sd()] = stat.agility | |
| − | + | args[sl()] = "Stamina" | |
| − | + | args[sd()] = stat.stamina | |
| − | + | end | |
| − | + | ||
| − | + | args[h()] = "Defensive Affinity" | |
| − | + | stat = stats.defensiveDamageAffinity | |
| − | + | if stat then | |
| − | + | args[sl()] = "Melee" | |
| − | + | args[sd()] = stat.Melee and toFixed(stat.Melee * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Magic" | |
| − | + | args[sd()] = stat.Magic and toFixed(stat.Magic * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Range" | |
| − | + | args[sd()] = stat.Range and toFixed(stat.Range * 100 - 100, 3) .. "%" | |
| − | + | args[sbreak()] = "yes" | |
| − | + | ||
| − | + | args[sl()] = "Piercing" | |
| − | + | args[sd()] = stat.Piercing and toFixed(stat.Piercing * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Blunt" | |
| − | + | args[sd()] = stat.Blunt and toFixed(stat.Blunt * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Slashing" | |
| − | + | args[sd()] = stat.Slashing and toFixed(stat.Slashing * 100 - 100, 3) .. "%" | |
| − | + | args[sl()] = "Fire" | |
| − | + | args[sd()] = stat.Fire and toFixed(stat.Fire * 100 - 100, 3) .. "%" | |
| + | args[sl()] = "Ice" | ||
| + | args[sd()] = stat.Ice and toFixed(stat.Ice * 100 - 100, 3) .. "%" | ||
| + | args[sl()] = "Nature" | ||
| + | args[sd()] = stat.Nature and toFixed(stat.Nature * 100 - 100, 3) .. "%" | ||
| + | args[sl()] = "Chaos" | ||
| + | args[sd()] = stat.Chaos and toFixed(stat.Chaos * 100 - 100, 3) .. "%" | ||
| + | args[sbreak()] = "yes" | ||
| + | end | ||
| + | |||
| + | args[h()] = "Abilities" | ||
| + | args[sd()] = createAbilityRotation(stats.grantedAbility, true) | ||
| + | |||
| + | args[h()] = "Set Bonus" | ||
| + | for id, desc in pairs(getModuleItemSet()._formatItemSets(stats.itemSet)) do | ||
| + | local enchant = getEnchant(id) | ||
| + | local counts = {} | ||
| + | for _, req in ipairs(enchant.setRequirements) do | ||
| + | if (req.strength > 0) then | ||
| + | table.insert(counts, req.count) | ||
| + | end | ||
| + | end | ||
| + | args[sl()] = "[[" .. enchant.name .. "]] [" .. table.concat(counts, ", ") .. "]" | ||
| + | args[sd()] = desc | ||
| + | args[sbreak()] = "yes" | ||
| + | end | ||
| + | end | ||
| + | |||
| + | local ammoStat = item.ammunitionMults | ||
| + | if ammoStat then | ||
| + | args[h()] = "Ammo Stats" | ||
| + | args[sl()] = 'Type' | ||
| + | args[sd()] = ammoStat.style | ||
| + | args[sl()] = 'Damage' | ||
| + | args[sd()] = ammoStat.damageMult .. 'x' | ||
| + | args[sl()] = 'Accuracy' | ||
| + | args[sd()] = ammoStat.accuracyMult .. 'x' | ||
| + | end | ||
| + | |||
| + | ---@return table|nil | ||
| + | local function getAugmentingByID(id) | ||
| + | local craftAug = getCraftAug(id) | ||
| + | if not craftAug then return nil end | ||
| + | return craftAug and craftAug.augmenting; | ||
| + | end | ||
| + | |||
| + | ---@return table|nil | ||
| + | local function getScrappingByID(id) | ||
| + | local craftAug = getCraftAug(id) | ||
| + | if not craftAug then return nil end | ||
| + | return craftAug.scrapping or getAugmentingByID(id) | ||
| + | end | ||
| + | |||
| + | local function canAugment(id) | ||
| + | return not item.blockAugmenting and getAugmentingByID(id) | ||
| + | end | ||
| + | |||
| + | local function canScrap(id) | ||
| + | return not item.blockResearching and getScrappingByID(id) | ||
| + | end | ||
| + | |||
| + | --TODO: add transform source | ||
| + | args[h()] = "Enchanting" | ||
| + | |||
| + | if getScrappingByID(item.id) then | ||
| + | if canAugment(item.id) or canScrap(item.id) then | ||
| + | args[l()] = dottedTooltip("Base XP", "Experience is increased by 10% for every augmentation level.") | ||
| + | args[d()] = lang:formatNum(20 * math.pow(getItemTier(item), 2)) | ||
| + | end | ||
| + | |||
| + | local craftAug = getCraftAug(item.id) | ||
| + | local costs = {} | ||
| + | for id, cost in pairs(craftAug.scrapping or {}) do | ||
| + | table.insert(costs, {id = id, text = lang:formatNum(cost) .. " " .. itemImage(id, true)}) | ||
| + | end | ||
| + | table.sort(costs, function(a, b) | ||
| + | return a.id < b.id | ||
| + | end) | ||
| + | for i = 1, #costs do | ||
| + | costs[i] = costs[i].text | ||
| + | end | ||
| + | |||
| + | args[sl()] = "Cost" | ||
| + | args[sd()] = table.concat(costs, "<br>") | ||
| + | |||
| + | ---@type table<string, AugmentingLoot>|nil | ||
| + | local augLoot = getAugLoot() | ||
| + | ---@type AugmentingLoot|nil | ||
| + | local augLootItem = augLoot and augLoot[tostring(item.id)] | ||
| + | local transformsTo = augLootItem and augLootItem.transforms | ||
| + | ---@type table<number, ItemTransform>|nil # the key is the id of the item that transforms | ||
| + | local transformsFrom | ||
| + | for id, keys in pairs(augLoot or {}) do | ||
| + | for _, transform in ipairs(keys.transforms or {}) do | ||
| + | if transform.newItemID == item.id then | ||
| + | transformsFrom = transformsFrom or {} | ||
| + | transformsFrom[tonumber(id)] = transform | ||
| + | end | ||
| + | end | ||
| + | end | ||
| + | |||
| + | args[h()] = "Augmentation" | ||
| + | if canAugment(item.id) or transformsFrom then | ||
| + | if item.equipmentStats then | ||
| + | local bonuses = {} | ||
| + | for _, bonus in pairs(item.equipmentStats.augmentationBonus or {}) do | ||
| + | table.insert(bonuses, "+" .. bonus.value .. " " .. getLabelFromAugBonus(bonus.stat)) | ||
| + | end | ||
| + | args[sl()] = "Bonus Stats" | ||
| + | args[sd()] = table.concat(bonuses, "<br>") | ||
| + | end | ||
| + | addTransforms(args, true, transformsFrom, transformsTo) | ||
| + | end | ||
| + | |||
| + | args[h()] = "Research" | ||
| + | if canScrap(item.id) or transformsFrom then | ||
| + | addResearch(args, item, transformsFrom, transformsTo) | ||
| + | end | ||
| + | end | ||
| + | |||
| + | local crafting = item.craftingStats | ||
| + | if crafting then | ||
| + | args[h()] = "Crafting" | ||
| + | |||
| + | args[l()] = "Category" | ||
| + | args[d()] = crafting.category | ||
| + | |||
| + | args[l()] = "Craftable" | ||
| + | args[d()] = not crafting.craftable and "No" or "" | ||
| + | |||
| + | args[l()] = "Level" | ||
| + | args[d()] = crafting.level | ||
| + | |||
| + | args[l()] = "Experience" | ||
| + | args[d()] = crafting.experience and lang:formatNum(crafting.experience) or "" | ||
| + | |||
| + | args[l()] = "Amount" | ||
| + | args[d()] = crafting.multiplier | ||
| + | |||
| + | args[sl()] = "Description" | ||
| + | args[sd()] = crafting.description | ||
| + | and '<p style="margin:auto;font-style:italic">' .. crafting.description .. '</p>' or "" | ||
| + | end | ||
| − | + | local ingredient = getCookingIngredient(item.id) | |
| − | + | if ingredient then | |
| − | + | args[h()] = "Cooking" | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | args[l()] = "Level" | |
| + | args[d()] = ingredient.level | ||
| − | + | args[l()] = "Difficulty" | |
| − | + | args[d()] = ingredient.difficulty | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | args[l()] = "Size" | |
| − | + | args[d()] = ingredient.size | |
| − | + | ||
| − | + | args[l()] = "Alchemy Size" | |
| − | + | args[d()] = ingredient.alchemySize | |
| − | + | ||
| − | + | args[l()] = "Category" | |
| − | + | args[d()] = table.concat(ingredient.ingredientTags or {}, "<br>") | |
| − | + | ||
| − | + | args[l()] = "Buff" | |
| − | + | args[d()] = getEnchantName(ingredient.cookingEnchantment) or getEnchantName(ingredient.alchemyEnchantment) or "" | |
| − | + | end | |
| − | + | ||
| − | + | local farming = getFamingStats(item.id) | |
| − | + | if farming then | |
| − | + | local drops = {} | |
| − | + | local yield = copyTable(getYield(item.id) or {}) | |
| − | + | table.sort(yield, function(a, b) | |
| − | + | return a.chance > b.chance | |
| − | + | end) | |
| − | + | for _, drop in ipairs(yield) do | |
| − | + | table.insert(drops, string.format( | |
| − | + | "%s–%s %s %s%%", | |
| − | + | drop.min, | |
| − | + | drop.max, | |
| − | + | itemImage(drop.id, true), | |
| − | + | toFixed(drop.chance * 100, 2) | |
| − | + | )) | |
| − | + | end | |
| − | + | ||
| − | + | args[h()] = "Farming" | |
| − | + | ||
| − | + | args[l()] = "Type" | |
| − | + | args[d()] = farming.type:gsub("^%l", string.upper) | |
| − | + | ||
| − | + | args[l()] = "Level" | |
| − | + | args[d()] = farming.requiredLevel | |
| − | + | ||
| − | + | args[l()] = "Experience" | |
| − | + | args[d()] = lang:formatNum(farming.experience) | |
| − | + | ||
| − | + | args[l()] = "Plot Size" | |
| − | + | args[d()] = string.format( | |
| − | + | "%dx%d%s", | |
| − | + | farming.width, | |
| − | + | farming.height, | |
| − | + | farming.maxWidth and string.format(" – %dx%d", farming.maxWidth, farming.maxHeight) or "" | |
| − | + | ) | |
| − | + | ||
| − | + | args[l()] = "Harvest Time" | |
| − | + | args[d()] = farming.time / 60 .. " minutes" | |
| − | + | ||
| − | + | args[l()] = "Yield" | |
| − | + | args[d()] = table.concat(drops, "<br>") | |
| − | + | end | |
| − | + | ||
| − | + | if item.extraTooltipInfo then | |
| − | + | args[h()] = "Tooltip" | |
| − | + | args[d()] = '<p style="margin:auto;font-style:italic;font-size:1.2em">' .. item.extraTooltipInfo .. '</p>' | |
| − | + | end | |
| − | + | ||
| − | + | for key, data in pairs(args) do | |
| − | + | if string.find(key, "data") then | |
| − | + | args[key] = tostring(data) | |
| − | + | end | |
| − | + | end | |
| − | + | ||
| − | + | return require('Module:Infobox').infobox(args) | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
end | end | ||
function p.item(frame) | function p.item(frame) | ||
| − | + | local args = frame:getParent().args | |
| − | + | return p._item(args) | |
end | end | ||
function p._item(args) | function p._item(args) | ||
| − | + | local name = args.name or args.title or args[1] or mw.title.getCurrentTitle().text | |
| − | + | local item = findItem(name) | |
| − | + | ||
| − | + | if not item then | |
| − | + | return "<div style=\"color:red\"> No item named '" .. name .. "'</div>. The Module:Items/data maybe outdated." | |
| − | + | end | |
| − | + | ||
| − | + | return createInfobox(item) | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
end | end | ||
return p | return p | ||
Latest revision as of 11:04, 5 September 2025
local p = {}
local module_names = {
item = 'Module:Items/data',
enchantment = 'Module:Enchantment/data',
location = 'Module:Location/data',
craftaug = 'Module:CraftingAugmenting/data',
farming = 'Module:Farming/data',
farmloot = 'Module:Farming loot/data',
augloot = 'Module:Augmenting loot/data',
cooking = 'Module:CookingList/data',
ability = 'Module:Abilities/data',
gameshop = 'Module:GameShopItems/data',
itemsets = 'Module:Item sets',
}
local loaded_modules = {}
local headerCount = 1
local labelCount = 1
local dataCount = 1
local cups = {
16000,16001,16002,16003,16004,16005,16006,16007,
16008,16009,16010,16011,16012,16013,16014,16015
}
---Checks if a table contains a specific value.
---@param t table
---@param value any
---@return boolean
local function hasValue(t, value)
for _, v in pairs(t) do
if value == v then
return true
end
end
return false
end
---Creates a deep copy of a tables and functions.
---Does not work with functions with upvalues and maybe userdata.
---@generic T
---@param t T The value to copy.
---@return T # A new value that is a deep copy of the original.
local function copyTable(t)
if type(t) ~= "table" then
return t
elseif type(t) == "function" then
return loadstring(string.dump(t))
end
local copy = {}
for k, v in pairs(t) do
copy[k] = copyTable(v)
end
return copy
end
---Formats a number up to a given number of decimal places, removing trailing zeros.
---@param num number|string The number to format.
---@param digits number The number of decimal places to include (must be a non-negative integer).
---@return string|nil # The formatted number as a string or nil if the input is not a valid number.
local function toFixed(num, digits)
local n = tonumber(num)
if not n then
return nil
end
digits = math.max(0, math.floor(digits))
local formatted = string.format("%." .. digits .. "f", n):gsub("%.?0+$", "")
return formatted
end
local function h()
local s = "header" .. headerCount
headerCount = headerCount + 1
labelCount = headerCount
dataCount = headerCount
return s
end
local function sbreak()
local s = "sbreak" .. headerCount
headerCount = headerCount + 1
labelCount = headerCount
dataCount = headerCount
return s
end
local function l()
local s = "label" .. labelCount
dataCount = labelCount
labelCount = labelCount + 1
headerCount = labelCount
return s
end
local function d()
local s = "data" .. dataCount
dataCount = dataCount + 1
headerCount = dataCount
labelCount = dataCount
return s
end
local function sl()
local s = "s" .. l()
return s
end
local function sd()
local s = "s" .. d()
return s
end
---@param data_type string
---@return table
function p.loadData(data_type)
local module_name = module_names[data_type]
if loaded_modules[module_name] == nil then
local status, module = pcall(mw.loadData, module_name)
if not status then
status, module = pcall(require, module_name)
end
if status then
loaded_modules[module_name] = module
else
error("Failed to load module: " .. data_type .. " / " .. tostring(module_name) .. "\n" .. module)
end
end
return loaded_modules[module_name]
end
local function getItem(id)
return p.loadData("item")[tostring(id)]
end
local function getEnchant(id)
return p.loadData("enchantment")[tostring(id)]
end
local function getEnchantName(id)
local enchantment = getEnchant(id)
return enchantment and enchantment.name
end
local function getModuleItemSet()
return p.loadData("itemsets")
end
local function getItemName(id)
return p.loadData("item")[tostring(id)].name
end
local function getCraftAug(id)
return p.loadData("craftaug")[tostring(id)]
end
---@param id string|number
---@return CookingItem|nil
local function getCookingIngredient(id)
return p.loadData("cooking")[tostring(id)]
end
---@param id string|number|nil # Returns all if nil
---@return AugmentingLoot|table<string, AugmentingLoot>|nil
local function getAugLoot(id)
local augloot = p.loadData("augloot")
return id == nil and augloot or augloot[tostring(id)]
end
---@param id string|number
---@return Ability|nil
local function getAbility(id)
return p.loadData("ability")[tostring(id)]
end
---@param id string|number
---@return FarmingLoot[]|nil
local function getYield(id)
return p.loadData("farmloot")[tostring(id)]
end
---@param id string|number|nil
---@return FarmingStats|FarmingStats[]|nil # farming stats for the given id or for all seeds if id is nil
local function getFamingStats(id)
local data = p.loadData("farming")
return id == nil and data or data[tostring(id)]
end
---@param itemId string|number # Item ID, is not the same as the `id` in the shop.
---@return nil
local function getGeneralShopItem(itemId)
local id = tonumber(itemId)
if not id then return nil end
local items = p.loadData("gameshop")
for _, item in pairs(items) do
if item.itemID == id then
return item
end
end
end
---@return Item|nil
local function findItem(name)
local lname = name:lower()
--Remove leading and trailing spaces.
lname = lname:gsub('^%s*(.-)%s*$', '%1')
for key, item in pairs(p.loadData("item")) do
if lname == item.name:lower() then
return item
end
end
end
local function dottedTooltip(name, tooltip)
return string.format(
'<span class="rt-commentedText tooltip tooltip-dotted" title="%s">%s</span>',
tooltip,
name
)
end
---Formats the given chance as a string presentation of percentage or 10k/100k fraction.
---@param chance number The chance to format. 1 equals 100%.
---@return string # The formatted chance as a string.
local function formatChance(chance)
local sign = chance < 0 and "-" or ""
local abs = math.abs(chance)
if abs < 0.001 then
return sign .. toFixed(abs * 10000, 1) .. "/10k"
elseif abs < 0.0001 then
return sign .. toFixed(abs * 100000, 1) .. "/100k"
end
return toFixed(chance * 100, 3) .. "%"
end
local function fullUrl(url)
local newUrl = url
if url:sub(1,5) == "https" then
return newUrl
end
if url:sub(1,1) ~= "/" then
newUrl = "/" .. newUrl
end
newUrl = "https://www.play.idlescape.com" .. newUrl
return newUrl
end
local function icon(name, url, word, size)
size = size or 20
url = fullUrl(url)
return string.format(
'[[%s|<img src="%s" alt="%s" width="%s">%s]]',
name,
url,
name,
size,
word and name or ""
)
end
---@param id string|number # Item ID.
---@param word boolean # If true, show the name of the item after the icon.
---@return string # The icon wikitext.
local function itemImage(id, word)
local item = p.loadData("item")[tostring(id)]
if not item then return "" end
local url = ""
if item.itemIcon then
url = item.itemIcon
else
url = item.itemImage
end
return icon(item.name, url, word)
end
---Creates an Wikitext link with an image inside a div.
---@param title string # The title of the link.
---@param imageAttributes table # The html attributes for the image.
---@param divCSS table # The CSS styles for the div.
---@return string # The Wikitext link with the image.
local function createWikitextImage(title, imageAttributes, divCSS)
divCSS = divCSS or {}
divCSS.display = divCSS.display or "inline-block" --Makes the div same size as the image
local e = mw.html.create("div")
:css(divCSS)
:tag("img")
:attr(imageAttributes)
:done()
e = tostring(e):gsub("<img(.-) */>", "<img%1>")
return string.format('[[%s|%s]]', title, e)
end
---Creates a Wikitext containing links and images for the abilities.
---@param abilities number[]|{id: number}[]|nil Array of ability IDs.
---@param unique boolean|nil Skips duplicate abilities if truthy, otherwise creates an element for each ability in order.
---@return string # The Wikitext containing links and images for the abilities.
local function createAbilityRotation(abilities, unique)
if not abilities then return "" end
local colors = {
Melee = "red",
Range = "green",
Magic = "blue",
}
local seenIds = {}
local images = {}
for _, id in ipairs(abilities) do
if type(id) == "table" then
id = id.id
end
if not seenIds[id] then
seenIds[id] = unique and true or false
local ability = getAbility(id)
if ability then
-- Check if the ability page exists and add tooltip if not
local exists = mw.title.new(ability.abilityName)
exists = exists and exists.exists
local attributes = {
src = fullUrl(ability.abilityImage),
alt = ability.abilityName .. ". " .. ability.description,
width = 30,
class = not exists and "tooltip" or nil,
title = not exists and ability.abilityName .. ". " .. ability.description or nil,
}
local css = {
["box-shadow"] = "0 0 2px 1px " .. (colors[ability.damageType] or "white"),
background = colors[ability.damageType] or "white",
margin = "5px"
}
table.insert(images, createWikitextImage(ability.abilityName, attributes, css))
end
end
end
return table.concat(images, "")
end
local function locationImage(id, word)
local loc = p.loadData("location")[tostring(id)]
local url = ""
if loc.locationImage then
url = loc.locationImage
else
return "[[" .. loc.name .. "]]"
end
return icon(loc.name, url, word)
end
local function gatheringSource(id)
local s = ""
for key, loc in pairs(p.loadData('location')) do
if loc.loot then
for key2, loot in pairs(loc.loot) do
if id == loot.id then
s = s .. locationImage(loc.locID, true)
s = s .. "<br>"
end
end
end
end
return s
end
local function farmingSource(id)
local s = ""
for key, item in pairs(p.loadData('item')) do
if item.farmingStats then
for key2, yield in pairs(item.farmingStats.yield) do
if id == yield.id then
s = s .. itemImage(item.id, true)
s = s .. "<br>"
end
end
end
end
return s
end
local function smithingSource(id)
local s = ""
local item = getItem(id)
if item.skill == "smithing" and item.name ~= 'Ichor' then
s = '[[Smithing]]<br>'
end
return s
end
local function cookingSource(id)
local s = ""
local item = getItem(id)
if (item.class == "cooking-ingredient" and not item.ingredientTags) or item.class == 'cookedFish' or item.name=='Ashes' then
s = '[[Cooking]]<br>'
end
return s
end
local function runecraftingSource(id)
local s = ""
local item = getItem(id)
if item.class == "cloth" or (item.class == 'rune' and item.requiredResources) then
s = '[[Runecrafting]]<br>'
end
return s
end
local function scrollcraftingSource(id)
local s = ""
local item = getItem(id)
if item.class == "enchanted-scroll" and item.level and item.level < 100 then
s = '[[Scrollcrafting]]<br>'
end
return s
end
local function craftingSource(id)
local s = ""
local item = getItem(id)
if item.craftable then
s = '[[Crafting]]<br>'
end
return s
end
local function findSource(id)
local s = ""
s = s .. gatheringSource(id)
-- s = s .. farmingSource(id)
s = s .. scrollcraftingSource(id)
s = s .. runecraftingSource(id)
s = s .. smithingSource(id)
s = s .. craftingSource(id)
s = s .. cookingSource(id)
s = s:gsub("<br>$", "")
return s
end
---@param item Item
---@return integer
local function getItemTier(item)
local maxRequiredLevel
if item.requiredLevel then
for _, level in pairs(item.requiredLevel) do
if maxRequiredLevel == nil or maxRequiredLevel < level then
maxRequiredLevel = level
end
end
maxRequiredLevel = math.floor(math.floor(maxRequiredLevel / 10))
end
return item.overrideItemTier or maxRequiredLevel or item.enchantmentTier or 1
end
local function getDustId(tier)
local AFFIX_DUST_PER_ITEM_TIER = {
550,
550,
550,
551,
552,
553,
554,
554,
554,
}
tier = (tonumber(tier) or 0) + 1 -- +1 for lua indexing
tier = math.floor(tier)
tier = math.min(math.max(tier, 1), 9)
return AFFIX_DUST_PER_ITEM_TIER[tier]
end
---@param rarity string|nil
---@return integer
local function getScrapId(rarity)
rarity = rarity or "common"
local scrapIds = {
common = 555,
uncommon = 556,
rare = 557,
epic = 558,
legendary = 559
}
return scrapIds[rarity]
end
---@param args table
---@param title string
---@param id string|number|false|nil # Primary loot id, skipped if falsy
---@param scrapping table|nil # data.chance, data.itemID (AugmentingLoot scrappingSuccess or scrappingFail)
local function addScrapping(args, title, id, scrapping)
local text = id and (itemImage(id, true) .. "<br>") or ""
if scrapping then
if scrapping.chance and scrapping.chance ~= 1 then
text = text .. formatChance(scrapping.chance) .. " "
end
if scrapping.minimum and scrapping.maximum then
text = text .. scrapping.minimum .. "–" .. scrapping.maximum .. " "
end
text = text .. itemImage(scrapping.itemID, true)
end
args[sl()] = title
args[sd()] = text
end
---@param args table # infobox args table
---@param augTransform boolean # true for augmenting, false for researching
---@param transformsFrom table<number, ItemTransform>|nil # table of source ID as key, and list of transforms as value
---@param transformsTo ItemTransform[]|nil # list of transforms
local function addTransforms(args, augTransform, transformsFrom, transformsTo)
if not (transformsFrom or transformsTo) then return end
---@param transform ItemTransform
---@param id number|false|nil # uses this for item's image instead of transform.newItemID
local function createTransform(transform, id)
if (transform.augmentingTransform or false) == augTransform then
local image = itemImage(id or transform.newItemID, true)
if transform.augmentingTransform then
return image .. " at level " .. (transform.augmentationLevel or 1)
else
return formatChance(transform.chance) .. " " .. image
end
end
end
---@param transforms table<number, ItemTransform>
---@param isKeyId boolean|nil # wether the table keys are item IDs or indicies
local function createTransformList(transforms, isKeyId)
local list = {}
for i, transform in pairs(transforms) do
local text = createTransform(transform, isKeyId and i)
table.insert(list, text)
end
return list
end
args[sbreak()] = "yes"
local list
if transformsFrom then
list = createTransformList(transformsFrom, true)
args[sl()] = "Transforms from"
args[sd()] = table.concat(list, "<br>")
end
if transformsTo then
list = createTransformList(transformsTo)
args[sl()] = "Transforms to"
args[sd()] = table.concat(list, "<br>")
end
end
---@param args table
---@param item Item
---@param transformsTo ItemTransform[]|nil # list of transforms
---@param transformsFrom table<number, ItemTransform>|nil # list of transforms
---@return boolean
local function addResearch(args, item, transformsTo, transformsFrom)
local isCup = hasValue(cups, item.id)
local canResearch = not getGeneralShopItem(item.id) or isCup
local craftAug = getCraftAug(item.id)
if craftAug and canResearch then
local augLoot = getAugLoot(tostring(item.id))
local tier = getItemTier(item)
local dustId = not item.noDustFromResearching and getDustId(tier) or nil
if not item.noScrapFromResearching then
local success = augLoot and augLoot.scrappingSuccess or nil
addScrapping(args, "Success", dustId, success)
end
-- General Shop items are hardcoded to never fail (CUPS atm)
if not isCup then
local scrapId = item.researchesIntoDust and dustId or getScrapId(item.rarity)
local fail = augLoot and augLoot.scrappingFail or nil
addScrapping(args, "Fail", scrapId, fail)
end
end
addTransforms(args, false, transformsTo, transformsFrom)
return craftAug and canResearch or transformsFrom ~= nil
end
---@param prefix 'offensiveDamageAffinity'|'offensiveAccuracyAffinityRating'|'defensiveDamageAffinity'
---@param description string|nil
---@param uppercase boolean|nil
---@return table<string, string>
local function createAffinityDictionary(prefix, description, uppercase)
description = description or ""
uppercase = uppercase or false
local allDamageTypes = {
"Melee",
"Magic",
"Range",
"Piercing",
"Blunt",
"Slashing",
"Chaos",
"Nature",
"Fire",
"Ice",
"Lightning",
"Poison",
"Typeless",
"Heal"
}
local affinities = {}
for _, damageType in ipairs(allDamageTypes) do
local key = string.format("%s.%s", prefix, damageType)
local val = string.format("%s %s", uppercase and damageType:upper() or damageType, description)
local affinity = val:gsub("%s+$", "")
affinities[key] = affinity
end
return affinities
end
local function getLabelFromAugBonus(bonusName)
local equipmentStatsToLabelMapping = {
["weaponBonus.strength"] = "Strength",
["weaponBonus.intellect"] = "Intellect",
["weaponBonus.dexterity"] = "Dexterity",
["offensiveCritical.chance"] = "Crit Chance",
["offensiveCritical.damageMultiplier"] = "Crit Mult",
["armorBonus.protection"] = "Protection",
["armorBonus.resistance"] = "Resistance",
["armorBonus.agility"] = "Agility",
["armorBonus.stamina"] = "Stamina",
["defensiveCritical.chance"] = "Crit Avoidance",
["defensiveCritical.damageMultiplier"] = "Crit Reduction",
["toolBoost.fishing"] = "Fishing",
["toolBoost.fishingBaitPower"] = "Bait Power",
["toolBoost.fishingReelPower"] = "Reel Power",
["toolBoost.fishingRarityPower"] = "Bonus Rarity",
["toolBoost.mining"] = "Mining",
["toolBoost.foraging"] = "Foraging",
["toolBoost.farming"] = "Farming",
["toolBoost.cooking"] = "Cooking",
["toolBoost.smithing"] = "Smithing",
["toolBoost.enchanting"] = "Enchanting",
["toolBoost.runecrafting"] = "Runecrafting"
}
for k, v in pairs(createAffinityDictionary("offensiveDamageAffinity", "") or {}) do
equipmentStatsToLabelMapping[k] = v
end
for k, v in pairs(createAffinityDictionary("offensiveAccuracyAffinityRating", "Accuracy") or {}) do
equipmentStatsToLabelMapping[k] = v
end
for k, v in pairs(createAffinityDictionary("defensiveDamageAffinity", "") or {}) do
equipmentStatsToLabelMapping[k] = v
end
return equipmentStatsToLabelMapping[bonusName] or bonusName
end
---@param item Item
local function createInfobox(item)
local lang = mw.language.getContentLanguage()
local args = {}
args.autoheaders = "y"
args.subbox = "no"
args.bodystyle = " "
args.title = item.name
local url = item.itemIcon or item.itemImage
args.image = '<img src="' .. fullUrl(url) .. '" width="150">'
if item.value then
args[l()] = "Vendor Value"
args[d()] = lang:formatNum(item.value) .. " " .. icon('Gold', "/images/gold_coin.png", false, 13)
end
--TODO: add this back when the market is fixed
--[[if item.tradeable then
args[l()] = icon('Market', "/images/ui/marketplace_icon.png")
local market = require("Module:Market")["_price"]({item.name, 1, 1})
if market then
args[d()] = addSeparator(market)
else
args[d()] = "Yes"
end
end]]
if item.rarity then
args[l()] = "Rarity"
args[d()] = item.rarity:gsub("^%l", string.upper)
end
if item.requiredLevel then
local levels = {}
for skill, level in pairs(item.requiredLevel) do
skill = skill:gsub("^%l", string.upper)
table.insert(levels, level .. " " .. skill)
end
args[l()] = "Level"
args[d()] = table.concat(levels, "<br>")
end
--TODO: findSource doesn't work so it's disabled for now. Does need a big rework
--args[l()] = "Source"
--args[d()] = findSource(item.id)
if item.heat then
args[l()] = itemImage(2, false)
args[d()] = lang:formatNum(item.heat)
end
if item.forcedEnchant then
args[l()] = "Enchantment"
args[d()] = "[[" .. getEnchantName(item.forcedEnchant) .. "]]"
args[l()] = "Enchantment Level"
args[d()] = item.forcedEnchantAmount
end
if item.enchantmentTier then
args[l()] = "Enchantment Slots"
args[d()] = item.enchantmentTier
end
local stats = item.equipmentStats
if stats then
local slot = stats.slot and stats.slot:gsub("^%l", string.upper)
args[l()] = "Slot"
args[d()] = stats.slot and slot
if stats.oneHanded == false then
args[l()] = "Two-handed"
args[d()] = "Yes"
end
if stats.attackSpeed then
args[l()] = "Attack Speed"
args[d()] = stats.attackSpeed .. "s"
end
if stats.toolBoost then
args[h()] = "Tool Stats"
for _, stat in ipairs(stats.toolBoost) do
if stat.boost ~= 0 then
local sign = stat.boost > 0 and "+" or ""
local boost = sign .. stat.boost
args[l()] = getLabelFromAugBonus("toolBoost." .. stat.skill)
args[d()] = boost
end
end
end
args[h()] = "Offensive Stats"
local stat = stats.offensiveCritical
if stat then
args[l()] = "Crit Chance"
args[d()] = toFixed(stat.chance * 100, 3) .. "%"
args[l()] = "Crit Multiplier"
args[d()] = stat.damageMultiplier .. "x"
end
stat = stats.weaponBonus
if stat then
args[sl()] = "Str"
args[sd()] = stat.strength
args[sl()] = "Int"
args[sd()] = stat.intellect
args[sl()] = "Dex"
args[sd()] = stat.dexterity
end
args[h()] = "Offensive Affinity"
stat = stats.offensiveDamageAffinity
if stat then
args[sl()] = "Melee"
args[sd()] = stat.Melee and toFixed(stat.Melee * 100 - 100, 3) .. "%"
args[sl()] = "Magic"
args[sd()] = stat.Magic and toFixed(stat.Magic * 100 - 100, 3) .. "%"
args[sl()] = "Range"
args[sd()] = stat.Range and toFixed(stat.Range * 100 - 100, 3) .. "%"
args[sbreak()] = "yes"
args[sl()] = "Piercing"
args[sd()] = stat.Piercing and toFixed(stat.Piercing * 100 - 100, 3) .. "%"
args[sl()] = "Blunt"
args[sd()] = stat.Blunt and toFixed(stat.Blunt * 100 - 100, 3) .. "%"
args[sl()] = "Slashing"
args[sd()] = stat.Slashing and toFixed(stat.Slashing * 100 - 100, 3) .. "%"
args[sl()] = "Fire"
args[sd()] = stat.Fire and toFixed(stat.Fire * 100 - 100, 3) .. "%"
args[sl()] = "Ice"
args[sd()] = stat.Ice and toFixed(stat.Ice * 100 - 100, 3) .. "%"
args[sl()] = "Nature"
args[sd()] = stat.Nature and toFixed(stat.Nature * 100 - 100, 3) .. "%"
args[sl()] = "Chaos"
args[sd()] = stat.Chaos and toFixed(stat.Chaos * 100 - 100, 3) .. "%"
args[sbreak()] = "yes"
end
args[h()] = "Accuracy"
stat = stats.offensiveAccuracyAffinityRating
if stat then
args[sl()] = "Melee"
args[sd()] = stat.Melee
args[sl()] = "Magic"
args[sd()] = stat.Magic
args[sl()] = "Range"
args[sd()] = stat.Range
args[sbreak()] = "yes"
args[sl()] = "Piercing"
args[sd()] = stat.Piercing
args[sl()] = "Blunt"
args[sd()] = stat.Blunt
args[sl()] = "Slashing"
args[sd()] = stat.Slashing
args[sl()] = "Fire"
args[sd()] = stat.Fire
args[sl()] = "Ice"
args[sd()] = stat.Ice
args[sl()] = "Nature"
args[sd()] = stat.Nature
args[sl()] = "Chaos"
args[sd()] = stat.Chaos
args[sbreak()] = "yes"
end
args[h()] = "Defensive Stats"
stat = stats.defensiveCritical
if stat then
args[l()] = "Crit Avoidance"
args[d()] = toFixed(stat.chance * 100, 3) .. "%"
args[l()] = "Crit Reduction"
args[d()] = stat.damageMultiplier
end
stat = stats.armorBonus
if stat then
args[sl()] = "Protection"
args[sd()] = stat.protection
args[sl()] = "Resistance"
args[sd()] = stat.resistance
args[sl()] = "Agility"
args[sd()] = stat.agility
args[sl()] = "Stamina"
args[sd()] = stat.stamina
end
args[h()] = "Defensive Affinity"
stat = stats.defensiveDamageAffinity
if stat then
args[sl()] = "Melee"
args[sd()] = stat.Melee and toFixed(stat.Melee * 100 - 100, 3) .. "%"
args[sl()] = "Magic"
args[sd()] = stat.Magic and toFixed(stat.Magic * 100 - 100, 3) .. "%"
args[sl()] = "Range"
args[sd()] = stat.Range and toFixed(stat.Range * 100 - 100, 3) .. "%"
args[sbreak()] = "yes"
args[sl()] = "Piercing"
args[sd()] = stat.Piercing and toFixed(stat.Piercing * 100 - 100, 3) .. "%"
args[sl()] = "Blunt"
args[sd()] = stat.Blunt and toFixed(stat.Blunt * 100 - 100, 3) .. "%"
args[sl()] = "Slashing"
args[sd()] = stat.Slashing and toFixed(stat.Slashing * 100 - 100, 3) .. "%"
args[sl()] = "Fire"
args[sd()] = stat.Fire and toFixed(stat.Fire * 100 - 100, 3) .. "%"
args[sl()] = "Ice"
args[sd()] = stat.Ice and toFixed(stat.Ice * 100 - 100, 3) .. "%"
args[sl()] = "Nature"
args[sd()] = stat.Nature and toFixed(stat.Nature * 100 - 100, 3) .. "%"
args[sl()] = "Chaos"
args[sd()] = stat.Chaos and toFixed(stat.Chaos * 100 - 100, 3) .. "%"
args[sbreak()] = "yes"
end
args[h()] = "Abilities"
args[sd()] = createAbilityRotation(stats.grantedAbility, true)
args[h()] = "Set Bonus"
for id, desc in pairs(getModuleItemSet()._formatItemSets(stats.itemSet)) do
local enchant = getEnchant(id)
local counts = {}
for _, req in ipairs(enchant.setRequirements) do
if (req.strength > 0) then
table.insert(counts, req.count)
end
end
args[sl()] = "[[" .. enchant.name .. "]] [" .. table.concat(counts, ", ") .. "]"
args[sd()] = desc
args[sbreak()] = "yes"
end
end
local ammoStat = item.ammunitionMults
if ammoStat then
args[h()] = "Ammo Stats"
args[sl()] = 'Type'
args[sd()] = ammoStat.style
args[sl()] = 'Damage'
args[sd()] = ammoStat.damageMult .. 'x'
args[sl()] = 'Accuracy'
args[sd()] = ammoStat.accuracyMult .. 'x'
end
---@return table|nil
local function getAugmentingByID(id)
local craftAug = getCraftAug(id)
if not craftAug then return nil end
return craftAug and craftAug.augmenting;
end
---@return table|nil
local function getScrappingByID(id)
local craftAug = getCraftAug(id)
if not craftAug then return nil end
return craftAug.scrapping or getAugmentingByID(id)
end
local function canAugment(id)
return not item.blockAugmenting and getAugmentingByID(id)
end
local function canScrap(id)
return not item.blockResearching and getScrappingByID(id)
end
--TODO: add transform source
args[h()] = "Enchanting"
if getScrappingByID(item.id) then
if canAugment(item.id) or canScrap(item.id) then
args[l()] = dottedTooltip("Base XP", "Experience is increased by 10% for every augmentation level.")
args[d()] = lang:formatNum(20 * math.pow(getItemTier(item), 2))
end
local craftAug = getCraftAug(item.id)
local costs = {}
for id, cost in pairs(craftAug.scrapping or {}) do
table.insert(costs, {id = id, text = lang:formatNum(cost) .. " " .. itemImage(id, true)})
end
table.sort(costs, function(a, b)
return a.id < b.id
end)
for i = 1, #costs do
costs[i] = costs[i].text
end
args[sl()] = "Cost"
args[sd()] = table.concat(costs, "<br>")
---@type table<string, AugmentingLoot>|nil
local augLoot = getAugLoot()
---@type AugmentingLoot|nil
local augLootItem = augLoot and augLoot[tostring(item.id)]
local transformsTo = augLootItem and augLootItem.transforms
---@type table<number, ItemTransform>|nil # the key is the id of the item that transforms
local transformsFrom
for id, keys in pairs(augLoot or {}) do
for _, transform in ipairs(keys.transforms or {}) do
if transform.newItemID == item.id then
transformsFrom = transformsFrom or {}
transformsFrom[tonumber(id)] = transform
end
end
end
args[h()] = "Augmentation"
if canAugment(item.id) or transformsFrom then
if item.equipmentStats then
local bonuses = {}
for _, bonus in pairs(item.equipmentStats.augmentationBonus or {}) do
table.insert(bonuses, "+" .. bonus.value .. " " .. getLabelFromAugBonus(bonus.stat))
end
args[sl()] = "Bonus Stats"
args[sd()] = table.concat(bonuses, "<br>")
end
addTransforms(args, true, transformsFrom, transformsTo)
end
args[h()] = "Research"
if canScrap(item.id) or transformsFrom then
addResearch(args, item, transformsFrom, transformsTo)
end
end
local crafting = item.craftingStats
if crafting then
args[h()] = "Crafting"
args[l()] = "Category"
args[d()] = crafting.category
args[l()] = "Craftable"
args[d()] = not crafting.craftable and "No" or ""
args[l()] = "Level"
args[d()] = crafting.level
args[l()] = "Experience"
args[d()] = crafting.experience and lang:formatNum(crafting.experience) or ""
args[l()] = "Amount"
args[d()] = crafting.multiplier
args[sl()] = "Description"
args[sd()] = crafting.description
and '<p style="margin:auto;font-style:italic">' .. crafting.description .. '</p>' or ""
end
local ingredient = getCookingIngredient(item.id)
if ingredient then
args[h()] = "Cooking"
args[l()] = "Level"
args[d()] = ingredient.level
args[l()] = "Difficulty"
args[d()] = ingredient.difficulty
args[l()] = "Size"
args[d()] = ingredient.size
args[l()] = "Alchemy Size"
args[d()] = ingredient.alchemySize
args[l()] = "Category"
args[d()] = table.concat(ingredient.ingredientTags or {}, "<br>")
args[l()] = "Buff"
args[d()] = getEnchantName(ingredient.cookingEnchantment) or getEnchantName(ingredient.alchemyEnchantment) or ""
end
local farming = getFamingStats(item.id)
if farming then
local drops = {}
local yield = copyTable(getYield(item.id) or {})
table.sort(yield, function(a, b)
return a.chance > b.chance
end)
for _, drop in ipairs(yield) do
table.insert(drops, string.format(
"%s–%s %s %s%%",
drop.min,
drop.max,
itemImage(drop.id, true),
toFixed(drop.chance * 100, 2)
))
end
args[h()] = "Farming"
args[l()] = "Type"
args[d()] = farming.type:gsub("^%l", string.upper)
args[l()] = "Level"
args[d()] = farming.requiredLevel
args[l()] = "Experience"
args[d()] = lang:formatNum(farming.experience)
args[l()] = "Plot Size"
args[d()] = string.format(
"%dx%d%s",
farming.width,
farming.height,
farming.maxWidth and string.format(" – %dx%d", farming.maxWidth, farming.maxHeight) or ""
)
args[l()] = "Harvest Time"
args[d()] = farming.time / 60 .. " minutes"
args[l()] = "Yield"
args[d()] = table.concat(drops, "<br>")
end
if item.extraTooltipInfo then
args[h()] = "Tooltip"
args[d()] = '<p style="margin:auto;font-style:italic;font-size:1.2em">' .. item.extraTooltipInfo .. '</p>'
end
for key, data in pairs(args) do
if string.find(key, "data") then
args[key] = tostring(data)
end
end
return require('Module:Infobox').infobox(args)
end
function p.item(frame)
local args = frame:getParent().args
return p._item(args)
end
function p._item(args)
local name = args.name or args.title or args[1] or mw.title.getCurrentTitle().text
local item = findItem(name)
if not item then
return "<div style=\"color:red\"> No item named '" .. name .. "'</div>. The Module:Items/data maybe outdated."
end
return createInfobox(item)
end
return p