Module:Infobox Item
Revision as of 13:47, 19 August 2025 by Demcookies (talk | contribs) (Move Enchanting Base XP from column to row and to before costs)
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',
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 {id: number, min: number, max: number, chance: number}[]|nil
local function getYield(id)
return p.loadData("farming")[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 = item.farmingStats
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()] = "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 .. " 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