Anonymous

Module:Items/SourceTables: Difference between revisions

From Melvor Idle
Merge Shop, Upgrade, Township and Superheat creation data into Creation Table (CT); Pull Arch & Gem data out of Loot Tables (LT) and into CT; Centered Qty and Chance in LT; Added 1/x values to Chances; Add output quantity for upgraded items in CT; Fix Ash reqs only displaying the first req; Make CT sortable & stickyHeader; Sorted CT by output quantity; Separate Rune costs in CT; Many other misc minor changes
m (Fix specialReq not being set per skill causing it to carry over to other entries)
(Merge Shop, Upgrade, Township and Superheat creation data into Creation Table (CT); Pull Arch & Gem data out of Loot Tables (LT) and into CT; Centered Qty and Chance in LT; Added 1/x values to Chances; Add output quantity for upgraded items in CT; Fix Ash reqs only displaying the first req; Make CT sortable & stickyHeader; Sorted CT by output quantity; Separate Rune costs in CT; Many other misc minor changes)
Line 30: Line 30:
end
end


function p._getCreationTable(item)
function p._getCreationTableData(item, tableData)
local skill = ''
if tableData == nil then tableData = {} end
local specialReq = nil
local source = nil
local time = 0
local maxTime = nil
local lvl = 0
local isAbyssal = false
local xp = 0
local qty = 1
local costs = nil
local chance = nil


local tableData = {}
local itemID = item.id
local itemID = item.id
--First figure out what skill is used to make this...
--First figure out what skill is used to make this...
Line 73: Line 62:
local skillData = SkillData[localSkillID]
local skillData = SkillData[localSkillID]
local skill = skillData.name
local skill = skillData.name
local lvl, isAbyssal, xp, qty, source, costs, time, maxTime, chance, specialReq = 0, false, 0, 1, nil, nil, 0, nil, nil, nil
local lvl, reqs, isAbyssal, xp, costs, qty, source, time, maxTime, weight, totalWeight = nil, nil, false, nil, nil, nil, nil, nil, nil, nil, nil, nil
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
local hasProduct = doesRecipeHaveItemID(recipe, itemID)
local hasProduct = doesRecipeHaveItemID(recipe, itemID)
Line 80: Line 69:
xp = recipe.baseAbyssalExperience or recipe.baseExperience
xp = recipe.baseAbyssalExperience or recipe.baseExperience
qty = recipe.baseQuantity or 1
qty = recipe.baseQuantity or 1
source = Icons.Icon({ skill, type='skill', class=(isAbyssal and 'abyss-icon' or '') })
reqs = Icons._SkillReq(skill, lvl, false, (isAbyssal and 'melvorItA:Abyssal' or nil))
if localSkillID == 'Farming' then
source = Icons.Icon({ skill, type='skill', class=(isAbyssal and 'abyss-icon' or nil) })
costs = { recipe.seedCost }
local catData = GameData.getEntityByID(skillData.categories, recipe.categoryID)
qty = 5 * catData.harvestMultiplier
end
-- Action time
-- Action time
if recipe.baseMinInterval ~= nil then
if recipe.baseMinInterval ~= nil then
Line 96: Line 81:
elseif skillData.baseInterval ~= nil then
elseif skillData.baseInterval ~= nil then
time = skillData.baseInterval / 1000
time = skillData.baseInterval / 1000
elseif localSkillID == 'Cartography' then
time = 5
end
end
-- Item chance and recipe costs
-- Custom Chance, Qty, and Costs data
if localSkillID == 'Firemaking' then
if localSkillID == 'Farming' then
costs = { recipe.seedCost }
local catData = GameData.getEntityByID(skillData.categories, recipe.categoryID)
qty = 5 * catData.harvestMultiplier
elseif localSkillID == 'Firemaking' then
local itemChanceData = GameData.getEntityByProperty(SkillData.Firemaking.primaryProducts, 'itemID', itemID)
local itemChanceData = GameData.getEntityByProperty(SkillData.Firemaking.primaryProducts, 'itemID', itemID)
if itemChanceData ~= nil then
if itemChanceData ~= nil then
chance = itemChanceData.chance .. '%'
weight = itemChanceData.chance
elseif itemID == 'melvorD:Generous_Fire_Spirit' then
elseif itemID == 'melvorD:Generous_Fire_Spirit' then
chance = '0.1%'
weight = 0.1
end
end


Line 121: Line 108:
end
end
elseif localSkillID == 'Cartography' then
elseif localSkillID == 'Cartography' then
time = 5
local costItem = Items.getItemByID(recipe.costs.items[1].id)
local costItem = Items.getItemByID(recipe.costs.items[1].id)
costs = Icons.Icon({ costItem.name, type='item', qty=1 })
costs = Icons.Icon({ costItem.name, type='item', qty=1 })
elseif localSkillID == 'Harvesting' then
elseif localSkillID == 'Harvesting' then
local totalWeight = 0
local itemChanceData = nil
local itemChanceData = nil
totalWeight = 0


for i, product in ipairs(recipe.products) do
for i, product in ipairs(recipe.products) do
Line 134: Line 123:


if itemChanceData ~= nil then
if itemChanceData ~= nil then
chance = Num.round2((itemChanceData.weight / totalWeight * 100), 2) .. '%'
weight = itemChanceData.weight
specialReq = itemChanceData.minIntensityPercent .. '% ' .. Icons.Icon({ recipe.name, type='vein', notext=true }) .. ' Intensity'
reqs = reqs .. '<br>' .. itemChanceData.minIntensityPercent .. '% ' .. Icons.Icon({ recipe.name, type='vein', notext=true }) .. ' Intensity'
end
end
end
end
-- Special requirements
-- Special requirements
if recipe.totalMasteryRequired ~= nil then
if recipe.totalMasteryRequired ~= nil then
specialReq = Icons.Icon({ 'Mastery', notext=true }) .. ' ' .. Num.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
reqs = reqs .. '<br>' .. Icons.Icon({ 'Mastery', notext=true }) .. ' ' .. Num.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
end
end
table.insert(tableData, {
table.insert(tableData, {
['skill'] = skill,
['skill'] = skill,
['lvl'] = lvl,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['xp'] = xp,
Line 152: Line 142:
['time'] = time,
['time'] = time,
['maxTime'] = maxTime,
['maxTime'] = maxTime,
['specialReq'] = specialReq,
['weight'] = weight,
['chance'] = chance
['totalWeight'] = totalWeight
})
})
-- Most recipes have a single item source or the item source data
-- Most recipes have a single item source or the item source data
Line 167: Line 157:
local skillData = SkillData[localSkillID]
local skillData = SkillData[localSkillID]
local skill = skillData.name
local skill = skillData.name
local lvl, isAbyssal, xp, qty, source, costs, time, maxTime, chance, specialReq = 0, false, 0, 1, nil, nil, 0, nil, nil, nil
local lvl, reqs, isAbyssal, xp, costs, qty, source, time, maxTime = nil, nil, false, nil, nil, nil, nil, nil, nil, nil
for i, recipe in ipairs(skillData.recipes) do
for i, recipe in ipairs(skillData.recipes) do
if recipe.productID == itemID or
if recipe.productID == itemID or
Line 175: Line 165:
xp = recipe.baseAbyssalExperience or recipe.baseExperience
xp = recipe.baseAbyssalExperience or recipe.baseExperience
qty = recipe.baseQuantity or 1
qty = recipe.baseQuantity or 1
source = Icons.Icon({ skill, type='skill', class=(isAbyssal and 'abyss-icon' or '') })
reqs = Icons._SkillReq(skill, lvl, false, (isAbyssal and 'melvorItA:Abyssal' or nil))
source = Icons.Icon({ skill, type='skill', class=(isAbyssal and 'abyss-icon' or nil) })
-- Action time
-- Action time
if recipe.baseMinInterval ~= nil then
if recipe.baseMinInterval ~= nil then
Line 192: Line 183:
local levelUnlock = GameData.getEntityByProperty(skillData.masteryLevelUnlocks, 'descriptionID', item.tier + 1)
local levelUnlock = GameData.getEntityByProperty(skillData.masteryLevelUnlocks, 'descriptionID', item.tier + 1)
if levelUnlock ~= nil then
if levelUnlock ~= nil then
specialReq = Icons._MasteryReq(item.name, levelUnlock.level, false)
reqs = reqs .. '<br>' .. Icons._MasteryReq(item.name, levelUnlock.level, false)
end
end
if localSkillID == 'Cooking' then
-- Cooking includes the required utility (fire, furnace, pot) as a special requirement
local cookingCatIcon = {
["melvorD:Fire"] = 'Normal Cooking Fire',
["melvorD:Furnace"] = 'Basic Furnace',
["melvorD:Pot"] = 'Basic Pot'
}
local categoryIconName, categoryName = cookingCatIcon[recipe.categoryID], nil
local recipeCategory = GameData.getEntityByID(SkillData.Cooking.categories, recipe.categoryID)
if recipeCategory ~= nil then
categoryName = recipeCategory.modifierName or recipeCategory.name
end
if categoryIconName ~= nil and categoryName ~= nil then
specialReq = Icons.Icon({ 'Cooking', categoryName, section = 'Cooking Upgrades', img = categoryIconName, type = 'upgrade' })
end
end
end
end
Line 243: Line 218:
costs = costs .. '<br>' .. (costLen == 1 and '' or 'and one of the following:<br>') .. table.concat(otherCostArray, "<br>'''OR''' ")
costs = costs .. '<br>' .. (costLen == 1 and '' or 'and one of the following:<br>') .. table.concat(otherCostArray, "<br>'''OR''' ")
end
end
specialReq = 'At least 1 ' .. Icons.Icon({ 'Summoning%23Summoning Marks', item.name, img=item.name, type='mark' }) .. ' mark discovered'
reqs = reqs .. '<br>At least 1 ' .. Icons.Icon({ 'Summoning', item.name, img=item.name, type='mark', section='Summoning Marks' }) .. ' mark discovered'
table.insert(tableData, {
table.insert(tableData, {
['skill'] = skill,
['skill'] = skill,
['lvl'] = lvl,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['xp'] = xp,
Line 252: Line 228:
['qty'] = qty,
['qty'] = qty,
['source'] = source,
['source'] = source,
['time'] = time,
['time'] = time
['specialReq'] = specialReq
})
})
-- Some items (such as Arrow shafts) have multiple recipes
-- Some items (such as Arrow shafts) have multiple recipes
Line 259: Line 234:
local reqPart, qtyPart = {}, {}
local reqPart, qtyPart = {}, {}
for j, altCost in ipairs(recipe.alternativeCosts) do
for j, altCost in ipairs(recipe.alternativeCosts) do
local costsStr = ''
for k, itemCost in ipairs(altCost.itemCosts) do
if k > 1 then costsStr = costsStr .. '<br>' end
local reqItem = Items.getItemByID(itemCost.id)
if reqItem == nil then
costsStr = costsStr .. itemCost.quantity .. 'x ?????'
else
costsStr = costsStr .. Icons.Icon({ reqItem.name, type='item', qty=itemCost.quantity })
end
end
costsStr = costsStr .. Common.getCostString({ ["items"] = {}, ["currencies"] = recipe.currencyCosts }, '')
table.insert(tableData, {
table.insert(tableData, {
['skill'] = skill,
['skill'] = skill,
['lvl'] = lvl,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['xp'] = xp,
['costs'] = costsStr,
['costs'] = Common.getCostString({ ["items"] = altCost.itemCosts, ["currencies"] = recipe.currencyCosts }),
['qty'] = Num.formatnum(qty * altCost.quantityMultiplier),
['qty'] = qty * altCost.quantityMultiplier,
['source'] = Icons.Icon({ skill, type='skill' }),
['source'] = source,
['time'] = time,
['time'] = time,
['maxTime'] = maxTime,
['maxTime'] = maxTime
['specialReq'] = specialReq
})
})
end
end
-- Finally, normal recipes with a single set of item costs
-- Finally, normal recipes with a single set of item costs
elseif type(recipe.itemCosts) == 'table' and not Shared.tableIsEmpty(recipe.itemCosts) then
elseif type(recipe.itemCosts) == 'table' and not Shared.tableIsEmpty(recipe.itemCosts) then
if localSkillID == 'Cooking' then
-- Cooking includes the required utility (fire, furnace, pot) as a special requirement
local cookingCatIcon = {
["melvorD:Fire"] = 'Normal Cooking Fire',
["melvorD:Furnace"] = 'Basic Furnace',
["melvorD:Pot"] = 'Basic Pot'
}
local categoryIconName, categoryName = cookingCatIcon[recipe.categoryID], nil
local recipeCategory = GameData.getEntityByID(SkillData.Cooking.categories, recipe.categoryID)
if recipeCategory ~= nil then
categoryName = recipeCategory.modifierName or recipeCategory.name
end
if categoryIconName ~= nil and categoryName ~= nil then
reqs = reqs .. '<br>' .. Icons.Icon({ 'Cooking', categoryName, section = 'Cooking Upgrades', img = categoryIconName, type = 'upgrade' })
end
end
table.insert(tableData, {
table.insert(tableData, {
['skill'] = skill,
['skill'] = skill,
['lvl'] = lvl,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['xp'] = xp,
['costs'] = recipe.itemCosts,
['costs'] = Common.getCostString({ ["items"] = recipe.itemCosts, ["currencies"] = recipe.currencyCosts }),
['qty'] = qty,
['qty'] = qty,
['source'] = Icons.Icon({ skill, type='skill' }),
['source'] = source,
['time'] = time,
['time'] = time,
['maxTime'] = maxTime,
['maxTime'] = maxTime
['specialReq'] = specialReq,
['currencyCost'] = recipe.currencyCosts
})
})
end
end
Line 309: Line 289:
if altSpell.produces == itemID then
if altSpell.produces == itemID then
table.insert(tableData, {
table.insert(tableData, {
['skill'] = 'Magic',
['skill'] = 'Alt Magic',
['lvl'] = altSpell.level,
['lvl'] = altSpell.level,
['reqs'] = Icons.Icon({'Alt Magic', type='skill', notext=true}) .. ' Level ' .. altSpell.level,
['isAbyssal'] = false,
['isAbyssal'] = false,
['xp'] = altSpell.baseExperience,
['xp'] = altSpell.baseExperience,
['costs'] = altSpell.runesRequired,
['costs'] = Magic._getAltSpellCostText(altSpell),
['qty'] = altSpell.productionRatio,
['qty'] = altSpell.productionRatio,
['source'] = Icons.Icon({ altSpell.name, type='spell' }),
['source'] = Icons.Icon({ altSpell.name, type=Magic._getSpellIconType(altSpell) }),
['time'] = 2,
['time'] = 2,
['altCosts'] = Magic._getAltSpellCostText(altSpell)
['runeCost'] = Magic._getSpellRunes(altSpell)
})
})
end
end
Line 331: Line 312:
['skill'] = 'Astrology',
['skill'] = 'Astrology',
['lvl'] = 1,
['lvl'] = 1,
['reqs'] = Icons._SkillReq('Astrology', 1, false, (isAbyssal and 'melvorItA:Abyssal' or nil)),
['isAbyssal'] = isAbyssal,
['isAbyssal'] = isAbyssal,
['qty'] = qty,
['qty'] = 1,
['xp'] = (isAbyssal and 1238 or 5), -- Use the (A)XP value for the first (abyssal) constellation
['xp'] = (isAbyssal and 1238 or 5), -- Use the XP value for the first constellation
['source'] = Icons.Icon({ 'Astrology', type='skill', class=(isAbyssal and 'abyss-icon' or '') }),
['source'] = Icons.Icon({ 'Astrology', type='skill', class=(isAbyssal and 'abyss-icon' or nil) }),
['time'] = 3,
['time'] = 3,
['chance'] = stardustChanceData.chance .. '%'
['weight'] = stardustChanceData.chance
})
})
end
end


if Shared.tableIsEmpty(tableData) then
-- Can we find this in an Archaeology digsite?
return ''
for i, drop in ipairs(p._getItemArchSources(item)) do
else
if drop.name ~= nil then
return p.buildCreationTable(tableData, item)
table.insert(tableData, {
['skill'] = 'Archaeology',
['lvl'] = drop.level,
['reqs'] = Icons._SkillReq('Archaeology', drop.level) .. ' ('..drop.size..')',
['isAbyssal'] = false,
['minqty'] = drop.minQty,
['qty'] = drop.maxQty,
['source'] = Icons.Icon({ drop.name, type='poi' }),
['time'] = 4,
['weight'] = drop.dropWt,
['totalWeight'] = drop.totalWt
--['expIcon'] = Icons.getExpansionIcon(drop.id)}),
})
end
end
end
end


function p.buildCreationTable(tableData, item)
-- Mining: Gems, and also Alt. Magic spells producing random gems
local showSource = false
if Shared.contains({'Gem', 'Superior Gem', 'Abyssal Gem'}, item.type) then
local showRequirements = false
local gemKeys = { 'randomGems', 'randomSuperiorGems', 'randomAbyssalGems' }
local showInputs = false
for i, gemKey in ipairs(gemKeys) do
local showOutputs = false
local thisGem, totalGemWeight = nil, 0
local showXP = false
for j, gem in ipairs(GameData.rawData[gemKey]) do
local showTime = false
totalGemWeight = totalGemWeight + gem.weight
local showChance = false
if gem.itemID == item.id then
local colspan = -1 -- colspan only needs to be set when there are 3+ columns in the table
thisGem = gem
 
end
for i, data in ipairs(tableData) do
end
if not showSource and tableData[1].source ~= tableData[i].source then
if thisGem ~= nil then
showSource = true
--local expIcon = ''
colspan = colspan + 1
local sourceTxt, lvl, isAbyssal = nil, nil, false
end
 
if not showRequirements and tableData[1].skill ~= tableData[i].skill then
if item.type == 'Abyssal Gem' then
showRequirements = true
sourceTxt = '[[Mining#Abyssal Gems|Abyssal Gem]]'
colspan = colspan + 1
lvl = 1
end
isAbyssal = true
if not showInputs and tableData[1].costs ~= tableData[i].costs then
elseif item.type == 'Superior Gem' then
showInputs = true
--expIcon = Icons.TotH()
colspan = colspan + 1
sourceTxt = '[[Mining#Superior Gems|Superior Gem]]'
end
-- Superior gems can only be found with Mining 100 or above
if not showOutputs and tableData[1].qty ~= tableData[i].qty then
lvl = 100
showOutputs = true
else
colspan = colspan + 1
sourceTxt = '[[Mining#Gems|Gem]]'
end
-- Gems can only be found with any Mining level
if not showXP and tableData[1].xp ~= tableData[i].xp then
lvl = 1
showXP = true
end
colspan = colspan + 1
table.insert(tableData, {
end
['skill'] = 'Mining',
if not showTime and tableData[1].time ~= tableData[i].time then
['lvl'] = lvl,
showTime = true
['reqs'] = Icons._SkillReq('Mining', lvl, false, (isAbyssal and 'melvorItA:Abyssal' or nil)),
colspan = colspan + 1
['isAbyssal'] = isAbyssal,
end
['minqty'] = thisGem.minQuantity,
if not showChance and tableData[1].chance ~= tableData[i].chance then
['qty'] = thisGem.maxQuantity,
showChance = true
['source'] = sourceTxt,
colspan = colspan + 1
['time'] = 3,
['weight'] = thisGem.weight,
['totalWeight'] = totalGemWeight,
--expIcon = expIcon
})
 
-- Check for Alt. Magic spells also
local producesKey = (gemKey == 'randomGems' and 'RandomGem') or (gemKey == 'randomSuperiorGems' and 'RandomSuperiorGem') or nil
if producesKey ~= nil then
for j, spell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
if spell.produces ~= nil and spell.produces == producesKey then
table.insert(tableData, {
['skill'] = 'Alt Magic',
['lvl'] = spell.level,
['reqs'] = Icons.Icon({'Alt Magic', type='skill', notext=true}) .. ' Level ' .. spell.level,
['minqty'] = thisGem.minQuantity,
['qty'] = thisGem.maxQuantity,
['source'] = Icons.Icon({ spell.name, type=Magic._getSpellIconType(spell) }),
['time'] = 2,
['weight'] = thisGem.weight,
['totalWeight'] = totalGemWeight,
--expIcon = Icons.getExpansionIcon(spell.id)
})
end
end
end
end
end
end
end
end


colspan = math.max(colspan, 1)
return tableData
end
 
function p.buildCreationTable(item, tableData)
if Shared.tableIsEmpty(tableData) then return '' end


local function addCostsRow(row, data, span)
table.sort(tableData, function(a, b) return (a.qty or 1) < (b.qty or 1) end)
local costsRow = row:tag('td'):attr('colspan', span)
if type(data.costs) == 'table' then
for i, mat in ipairs(data.costs) do
if i > 1 then costsRow:tag('br') end
local matItem = Items.getItemByID(mat.id)
if matItem == nil then
costsRow:wikitext(mat.quantity .. 'x ?????')
else
costsRow:wikitext(Icons.Icon({ matItem.name, type='item', qty=mat.quantity }))
end
end
if data.currencyCost ~= nil then
costsRow:wikitext('<br>' .. Common.getCostString({ ["items"] = {}, ["currencies"] = data.currencyCost }, ''))
end
else
costsRow:wikitext(data.costs)
end


if data.altCosts ~= nil then
local showSource = false
costsRow:wikitext('<br>' .. data.altCosts:gsub(', ', '<br>'))
local showRequirements = false
end
local showInputs = false
end
local showRunes = false
local showOutputs = false
local showXP = false
local showTime = false
local showChance = false
local colspan = -1 -- colspan only needs to be set when there are 3+ columns in the table


local resultTable = mw.html.create('table')
for i, data in ipairs(tableData) do
resultTable:addClass('wikitable')
if not showSource and tableData[1].source ~= tableData[i].source then
local tableHeader = resultTable:tag('tr')
showSource = true
 
colspan = colspan + 1
if showSource then tableHeader:tag('th'):wikitext('Source') end
end
if showRequirements then tableHeader:tag('th'):wikitext('Requires') end
if not showRequirements and tableData[1].reqs ~= tableData[i].reqs then
if showInputs then tableHeader:tag('th'):wikitext('Inputs') end
showRequirements = true
if showOutputs then tableHeader:tag('th'):wikitext('Outputs') end
colspan = colspan + 1
if showXP then tableHeader:tag('th'):wikitext('Exp') end
if showTime then tableHeader:tag('th'):wikitext('Time') end
if showChance then tableHeader:tag('th'):wikitext('Chance') end
 
for i, data in ipairs(tableData) do
local recipeRow = resultTable:tag('tr')
 
if showSource then recipeRow:tag('td'):wikitext(data.source) end
if showRequirements then
local tableReqs = recipeRow:tag('td'):wikitext(Icons._SkillReq(data.skill, data.lvl, false, (data.isAbyssal and "melvorItA:Abyssal" or nil)))
 
if data.specialReq ~= nil then
tableReqs:wikitext('<br>' .. data.specialReq)
end
end
end
 
if not showInputs and tableData[1].costs ~= tableData[i].costs then
if showInputs and data.costs ~= nil then
showInputs = true
addCostsRow(recipeRow, data, 1)
colspan = colspan + 1
elseif showInputs then
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na')
end
end
 
if not showRunes and  tableData[1].runeCost ~= tableData[i].runeCost then
if showOutputs then recipeRow:tag('td'):wikitext(Icons.Icon({ item.name, type='item', notext=true, qty=data.qty })):addClass('center') end
showRunes = true
if showXP then
colspan = colspan + 1
recipeRow:tag('td')
end
:wikitext(Icons.Icon({ data.skill, notext=true, type='skill', class=(data.isAbyssal and 'abyss-icon' or '') }))
if not showOutputs and (tableData[1].qty ~= tableData[i].qty or tableData[1].contents ~= tableData[i].contents) then
:wikitext(' ' .. Num.formatnum(data.xp) .. (data.isAbyssal and ' AXP' or ' XP'))
showOutputs = true
colspan = colspan + 1
end
if not showXP and tableData[1].xp ~= tableData[i].xp then
showXP = true
colspan = colspan + 1
end
if not showTime and tableData[1].time ~= tableData[i].time then
showTime = true
colspan = colspan + 1
end
if not showChance and tableData[1].weight ~= tableData[i].weight then
showChance = true
colspan = colspan + 2
end
end
if showTime then recipeRow:tag('td'):wikitext(Shared.timeString(data.time, true)):addClass('center') end
if showChance then recipeRow:tag('td'):wikitext((data.chance or '100%')):addClass('center') end
end
if not showSource and tableData[1].source ~= nil then
resultTable:tag('tr')
:tag('th'):wikitext('Source'):css('text-align', 'right')
:tag('td'):attr('colspan', colspan):wikitext(tableData[1].source)
end
end
if not showRequirements and tableData[1].skill ~= nil and tableData[1].lvl ~= nil then
local reqRow = resultTable:tag('tr')
:tag('th'):wikitext('Requires'):css('text-align', 'right')


local reqData = reqRow:tag('td'):attr('colspan', colspan)
colspan = math.max(colspan, 1)
reqData:wikitext(Icons._SkillReq(tableData[1].skill, tableData[1].lvl, false, (tableData[1].isAbyssal and "melvorItA:Abyssal" or nil)))


if tableData[1].specialReq ~= nil then
local function addCostsRow(row, data, span)
reqData:wikitext('<br>' .. tableData[1].specialReq)
local costsRow = row:tag('td'):attr('colspan', span)
end
if type(data.costs) == 'table' then
end
for i, mat in ipairs(data.costs) do
if not showInputs and tableData[1].costs ~= nil then
if i > 1 then costsRow:tag('br') end
local costRow = resultTable:tag('tr')
local matItem = Items.getItemByID(mat.id)
:tag('th'):wikitext('Inputs'):css('text-align', 'right')
if matItem == nil then
costsRow:wikitext(mat.quantity .. 'x ?????')
addCostsRow(costRow, tableData[1], colspan)
else
costsRow:wikitext(Icons.Icon({ matItem.name, type='item', qty=mat.quantity }))
end
end
else
local costStr = data.costs:gsub(', ', '<br>')
costsRow:wikitext(costStr)
end
end
end
if not showOutputs and tableData[1].qty ~= nil then
 
resultTable:tag('tr')
local resultTable = mw.html.create('table')
:tag('th'):wikitext('Outputs'):css('text-align', 'right')
resultTable:addClass('wikitable stickyHeader')
:tag('td'):attr('colspan', colspan):wikitext(Icons.Icon({ item.name, type='item', qty=tableData[1].qty }))
local tableHeader = resultTable:tag('tr'):addClass('headerRow-0')
 
local makeSortable = Shared.contains({ showSource, showRequirements, showInputs, showRunes, showOutputs, showXP, showTime, showChance }, true)
if makeSortable then
resultTable:addClass('sortable')
end
end
if not showXP and tableData[1].xp ~= nil then
resultTable:tag('tr')
:tag('th'):wikitext('Base Exp'):css('text-align', 'right')
:tag('td'):attr('colspan', colspan):wikitext(Num.formatnum(tableData[1].xp) .. (tableData[1].isAbyssal and ' AXP' or ' XP'))
end
if not showTime and tableData[1].time ~= nil then
resultTable:tag('tr')
local timeHeader = resultTable:tag('th'):wikitext('Base Time'):css('text-align', 'right')


local timeData = timeHeader:tag('td'):attr('colspan', colspan)
if showSource then tableHeader:tag('th'):wikitext('Source') end
timeData:wikitext(Shared.timeString(tableData[1].time, true))
if showRequirements then tableHeader:tag('th'):wikitext('Requires') end
if showInputs then tableHeader:tag('th'):wikitext('Costs') end
if showRunes then tableHeader:tag('th'):wikitext('Runes') end
if showOutputs then tableHeader:tag('th'):wikitext('Outputs') end
if showXP then tableHeader:tag('th'):wikitext('Exp') end
if showTime then tableHeader:tag('th'):wikitext('Time') end
if showChance then tableHeader:tag('th'):wikitext('Chance'):attr('colspan', 2) end


if tableData[1].maxTime ~= nil and tableData[1].maxTime > tableData[1].time then
if makeSortable then
timeData:wikitext(' - ' .. Shared.timeString(tableData[1].maxTime, true))
-- Populate table data with any unique entries (Ex: Ash's Inputs, Outputs, Exp, Time)
end
for i, data in ipairs(tableData) do
end
local recipeRow = resultTable:tag('tr')
if not showChance and tableData[1].chance ~= nil then
resultTable:tag('tr')
:tag('th'):wikitext('Base Chance'):css('text-align', 'right')
:tag('td'):attr('colspan', colspan):wikitext(tableData[1].chance)
end


return tostring(resultTable)
if showSource then recipeRow:tag('td'):wikitext(data.source):attr('data-sort-value', data.skill) end
end


function p.getCreationTable(frame)
if showRequirements then
local itemName = frame.args ~= nil and frame.args[1] or frame
if data.reqs ~= nil then
local item = Items.getItem(itemName)
recipeRow:tag('td'):wikitext(data.reqs):attr('data-sort-value', (data.lvl or 0))
if item == nil then
else
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na'):attr('data-sort-value', 0)
end
end
end


return p._getCreationTable(item)
if showInputs then
end
if data.costs ~= nil then
addCostsRow(recipeRow, data, 1)
else
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na')
end
end


function p._getItemSources(item, asList, addCategories, separator)
if showRunes then
local lineArray = {}
if data.runeCost ~= nil then
local categoryArray = {}
recipeRow:tag('td'):wikitext(data.runeCost):addClass('center')
local sep = separator or ','
else
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na')
end
end
 
if showOutputs then
local outputData = recipeRow:tag('td'):attr('data-sort-value', (data.qty or 1))
if data.contents ~= nil then
outputData:wikitext(data.contents)
if data.center then outputData:addClass('center') end
elseif data.qty ~= nil then
if data.minqty ~= nil and data.minqty ~= data.qty then
outputRow:wikitext((data.minqty ~= nil and (Num.formatnum(data.minqty) .. ' - ') or ''))
end
outputData:wikitext(Icons.Icon({ item.name, type='item', notext=true, qty=(data.qty or 1) })):addClass('center'):attr('data-sort-value', (data.qty or 1))
else
outputData:wikitext('N/A'):addClass('table-na')
end
end
 
if showXP then
if data.skill ~= nil and data.xp ~= nil then
local iconClass = (data.isAbyssal and 'abyss-icon' or nil)
local xpText = (data.isAbyssal and ' AXP' or ' XP')
recipeRow:tag('td'):attr('data-sort-value', data.xp)
:wikitext(Icons.Icon({ data.skill, notext=true, type='skill', class=iconClass }))
:wikitext(' ' .. Num.formatnum(data.xp) .. xpText)
else
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na'):attr('data-sort-value', 0)
end
end


--Alright, time to go through all the ways you can get an item...
if showTime then
--First up: Can we kill somebody and take theirs?
if data.time ~= nil then
local killStrPart = {}
recipeRow:tag('td'):wikitext(Shared.timeString(data.time, true)):addClass('center'):attr('data-sort-value', data.time)
for i, monster in ipairs(GameData.rawData.monsters) do
else
local isDrop = false
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na')
if monster.bones ~= nil and monster.bones.itemID == item.id and Monsters._getMonsterBones(monster) ~= nil then
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
isDrop = true
elseif monster.barrierPercent ~= nil and 'melvorAoD:Barrier_Dust' == item.id and not Monsters._isDungeonOnlyMonster(monster) then
-- Item is Barrier Dust and is not a dungeon exclusive monster
isDrop = true
elseif monster.lootTable ~= nil then
-- If the monster has a loot table, check if the item we are looking for is in there
-- Dungeon exclusive monsters don't count as they are either:
--   - A monster before the boss, which doesn't drop anything except shards (checked above)
--  - A boss monster, whose drops are accounted for in data from Areas instead
for j, loot in ipairs(monster.lootTable) do
if loot.itemID == item.id and not Monsters._isDungeonOnlyMonster(monster) then
isDrop = true
break
end
end
end
end
end
 
if isDrop then
if showChance then
-- Item drops when the monster is killed
if data.weight ~= nil then
local iconName = monster.name
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
if SourceOverrides[monster.id] ~= nil then
local chance = data.weight / (data.totalWeight or 100) * 100
iconName = SourceOverrides[monster.id]
local fmt = (chance < 0.10 and '%.2g') or '%.2f'
local percent = string.format(fmt, chance)
 
recipeRow:tag('td'):wikitext(Num.fraction(data.weight, (data.totalWeight or 100))):addClass('center'):attr('data-sort-value', percent)
recipeRow:tag('td'):wikitext(percent .. '%'):addClass('center')
else
recipeRow:tag('td'):wikitext('100%'):addClass('center'):attr('colspan', 2):attr('data-sort-value', 100)
end
end
end
table.insert(killStrPart, Icons.Icon({iconName, type='monster', notext=true}))
end
end
end
end
-- Is the item dropped from any dungeon?
 
local dungeonStrPart = {}
-- Add all non-unique data below the table data (Ex: Ash's Source, Requires, Chance)
local dungeonEntities = {
if not showSource and tableData[1].source ~= nil then
['Dungeon'] = GameData.rawData.dungeons,
resultTable:tag('tr')
['The Abyss'] = GameData.rawData.abyssDepths
:tag('th'):wikitext('Source'):css('text-align', 'right')
}
:tag('td'):attr('colspan', colspan):wikitext(tableData[1].source)
for entity, dungeons in pairs(dungeonEntities) do
end
for i, dungeon in ipairs(dungeons) do
 
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
if not showRequirements and tableData[1].reqs ~= nil then
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
local reqRow = resultTable:tag('tr')
table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='combatArea', notext=true}))
:tag('th'):wikitext('Requires'):css('text-align', 'right')
elseif dungeon.eventID ~= nil then
:tag('td'):wikitext(tableData[1].reqs):attr('colspan', colspan)
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
end
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
 
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
if not showInputs and tableData[1].costs ~= nil then
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
local costRow = resultTable:tag('tr')
if item.id == itemRewardID then
:tag('th'):wikitext('Costs'):css('text-align', 'right')
local dungPrefix = (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or eventCycle .. (eventCycle == 1 and ' cycle' or ' cycles') .. ' of ')
 
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({dungeon.name, type='combatArea', notext=true}))
addCostsRow(costRow, tableData[1], colspan)
break
end
end
 
end
if not showRunes and type(tableData[1].runeCost) == 'string' then
end
resultTable:tag('tr')
:tag('th'):wikitext('Runes'):css('text-align', 'right')
:tag('td'):wikitext(tableData[1].runeCost):addClass('center')
end
 
if not showOutputs and (tableData[1].qty ~= nil or tableData[1].contents ~= nil) then
local outputRow = resultTable:tag('tr')
:tag('th'):wikitext('Outputs'):css('text-align', 'right')
 
if tableData[1].contents ~= nil then
outputRow:tag('td'):wikitext(tableData[1].contents)
else
local outputData = outputRow:tag('td'):attr('colspan', colspan)
if tableData[1].minqty ~= nil and tableData[1].minqty ~= tableData[1].qty then
outputData:wikitext((tableData[1].minqty ~= nil and (Num.formatnum(tableData[1].minqty) .. ' - ') or ''))
end
end
outputData:wikitext(Icons.Icon({ item.name, type='item', qty=(tableData[1].qty or 1) }))
end
end
end
end
 
for i, stronghold in ipairs(GameData.rawData.strongholds) do
if not showXP and tableData[1].xp ~= nil then
for tier, tierData in pairs(stronghold.tiers) do
local xpText = (tableData[1].isAbyssal and ' AXP' or ' XP')
if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
resultTable:tag('tr')
for i, reward in ipairs(tierData.rewards.items) do
:tag('th'):wikitext('Base Exp'):css('text-align', 'right')
if reward.id == item.id then
:tag('td'):attr('colspan', colspan):wikitext(Num.formatnum(tableData[1].xp) .. xpText)
table.insert(dungeonStrPart, Icons.Icon({stronghold.name, type='combatArea', notext=true}))
end
end
 
end
if not showTime and tableData[1].time ~= nil then
end
resultTable:tag('tr')
local timeHeader = resultTable:tag('th'):wikitext('Base Time'):css('text-align', 'right')
 
local timeData = timeHeader:tag('td'):attr('colspan', colspan)
:wikitext(Shared.timeString(tableData[1].time, true))
 
if tableData[1].maxTime ~= nil and tableData[1].maxTime > tableData[1].time then
timeData:wikitext(' - ' .. Shared.timeString(tableData[1].maxTime, true))
end
end
end
end


if not Shared.tableIsEmpty(dungeonStrPart) then
if not showChance and tableData[1].weight ~= nil then
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, sep))
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
end
local chance = tableData[1].weight / (tableData[1].totalWeight or 100) * 100
if not Shared.tableIsEmpty(killStrPart) then
local fmt = (chance < 0.10 and '%.2g') or '%.2f'
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, sep))
local percent = string.format(fmt, chance)
local chanceData = resultTable:tag('tr')
:tag('th'):wikitext('Base Chance'):css('text-align', 'right')
:tag('td'):attr('colspan', colspan)
:wikitext(Num.fraction(tableData[1].weight, (tableData[1].totalWeight or 100)) .. ' (' .. percent .. '%)')
end
end


-- Can we find it in an openable item?
return tostring(resultTable)
local lootPart = {}
end
for i, item2 in ipairs(GameData.rawData.items) do
 
if item2.dropTable ~= nil then
function p.getCreationTable(frame)
for j, loot in ipairs(item2.dropTable) do
local itemName = frame.args ~= nil and frame.args[1] or frame
if loot.itemID == item.id then
local item = Items.getItem(itemName)
table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true}))
if item == nil then
break
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end
end
end
end
end


if not Shared.tableIsEmpty(lootPart) then
return p.buildCreationTable(p._getCreationTableData(item), item)
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, sep))
end
end


-- Is the item a result of upgrading/downgrading another item?
function p._getItemSources(item, asList, addCategories, separator)
local upgradePart = { up = {}, down = {} }
local lineArray = {}
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
local categoryArray = {}
if item.id == upgrade.upgradedItemID then
local sep = separator or ','
local key = (upgrade.isDowngrade and 'down' or 'up')
for j, rootItemID in ipairs(upgrade.rootItemIDs) do
local rootItem = Items.getItemByID(rootItemID)
if rootItem ~= nil then
table.insert(upgradePart[key], Icons.Icon({rootItem.name, type='item', notext=true}))
end
end
end
end


local upgradeCat = false
--Alright, time to go through all the ways you can get an item...
for catName, parts in pairs(upgradePart) do
--First up: Can we kill somebody and take theirs?
if not Shared.tableIsEmpty(parts) then
local killStrPart = {}
if not upgradeCat then
for i, monster in ipairs(GameData.rawData.monsters) do
table.insert(categoryArray, '[[Category:Upgraded Items]]')
local isDrop = false
upgradeCat = true
if monster.bones ~= nil and monster.bones.itemID == item.id and Monsters._getMonsterBones(monster) ~= nil then
end
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading'
isDrop = true
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, sep))
elseif monster.barrierPercent ~= nil and 'melvorAoD:Barrier_Dust' == item.id and not Monsters._isDungeonOnlyMonster(monster) then
-- Item is Barrier Dust and is not a dungeon exclusive monster
isDrop = true
elseif monster.lootTable ~= nil then
-- If the monster has a loot table, check if the item we are looking for is in there
-- Dungeon exclusive monsters don't count as they are either:
--  - A monster before the boss, which doesn't drop anything except shards (checked above)
--  - A boss monster, whose drops are accounted for in data from Areas instead
for j, loot in ipairs(monster.lootTable) do
if loot.itemID == item.id and not Monsters._isDungeonOnlyMonster(monster) then
isDrop = true
break
end
end
end
if isDrop then
-- Item drops when the monster is killed
local iconName = monster.name
if SourceOverrides[monster.id] ~= nil then
iconName = SourceOverrides[monster.id]
end
table.insert(killStrPart, Icons.Icon({iconName, type='monster', notext=true}))
end
end
end
end
 
-- Is the item dropped from any dungeon?
--Next: Can we take it from somebody else -without- killing them?
local dungeonStrPart = {}
local thiefItems = Skills.getThievingSourcesForItem(item.id)
local dungeonEntities = {
if type(thiefItems) == 'table' then
['Dungeon'] = GameData.rawData.dungeons,
local includedNPCs = {}
['The Abyss'] = GameData.rawData.abyssDepths
local thiefPart = {}
}
for i, thiefRow in ipairs(thiefItems) do
for entity, dungeons in pairs(dungeonEntities) do
if thiefRow.npc == 'all' then
for i, dungeon in ipairs(dungeons) do
--if 'all' is the npc, this is a rare item so just say 'Thieving level 1'
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
table.insert(lineArray, Icons._SkillReq('Thieving', 1))
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
elseif not Shared.contains(includedNPCs, thiefRow.npc) then
table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='combatArea', notext=true}))
table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
elseif dungeon.eventID ~= nil then
table.insert(includedNPCs, thiefRow.npc)
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
end
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
end
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
if not Shared.tableIsEmpty(thiefPart) then
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, sep))
if item.id == itemRewardID then
local dungPrefix = (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or eventCycle .. (eventCycle == 1 and ' cycle' or ' cycles') .. ' of ')
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({dungeon.name, type='combatArea', notext=true}))
break
end
end
end
end
end
end
end
end
-- Can we get this item by casting an Alt. Magic spell?
for i, stronghold in ipairs(GameData.rawData.strongholds) do
local castPart = {}
for tier, tierData in pairs(stronghold.tiers) do
for i, spell in ipairs(Magic.getSpellsProducingItem(item.id)) do
if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
table.insert(castPart, Icons.Icon({spell.name, type=Magic._getSpellIconType(spell), notext=true}))
for i, reward in ipairs(tierData.rewards.items) do
end
if reward.id == item.id then
if not Shared.tableIsEmpty(castPart) then
table.insert(dungeonStrPart, Icons.Icon({stronghold.name, type='combatArea', notext=true}))
table.insert(lineArray, 'Casting: ' .. table.concat(castPart, sep))
end
end
end
end
end
 
if not Shared.tableIsEmpty(dungeonStrPart) then
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, sep))
end
if not Shared.tableIsEmpty(killStrPart) then
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, sep))
end
end


--Check if we can make it ourselves
-- Can we find it in an openable item?
local skillIDs = {
local lootPart = {}
['Gathering'] = {
for i, item2 in ipairs(GameData.rawData.items) do
['Woodcutting'] = { recipeKey = 'trees' },
if item2.dropTable ~= nil then
['Fishing'] = { recipeKey = 'fish' },
for j, loot in ipairs(item2.dropTable) do
['Firemaking'] = { recipeKey = 'logs' },
if loot.itemID == item.id then
['Mining'] = { recipeKey = 'rockData' },
table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true}))
['Farming'] = { recipeKey = 'recipes' },
break
['Harvesting'] = { recipeKey = 'veinData' }
end
},
end
['Artisan'] = {
end
['Cooking'] = { },
end
['Smithing'] = { },
 
['Fletching'] = { },
if not Shared.tableIsEmpty(lootPart) then
['Crafting'] = { },
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, sep))
['Runecrafting'] = { },
end
['Herblore'] = { },
['Summoning'] = { }
}
}


-- Gathering skills
-- Is the item a result of upgrading/downgrading another item?
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
local upgradePart = { up = {}, down = {} }
local skillData = SkillData[localSkillID]
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
local skill = skillData.name
if item.id == upgrade.upgradedItemID then
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
local key = (upgrade.isDowngrade and 'down' or 'up')
local hasProduct = doesRecipeHaveItemID(recipe, item.id)
for j, rootItemID in ipairs(upgrade.rootItemIDs) do
if hasProduct then
local rootItem = Items.getItemByID(rootItemID)
if localSkillID == 'Farming' and recipe.seedCost ~= nil then
if rootItem ~= nil then
local seedItem = Items.getItemByID(recipe.seedCost.id)
table.insert(upgradePart[key], Icons.Icon({rootItem.name, type='item', notext=true}))
if seedItem ~= nil then
table.insert(lineArray, 'Growing: ' .. Icons.Icon({seedItem.name, type='item', notext='true'}))
end
else
local level, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe)
table.insert(lineArray, Icons._SkillReq(skill, level, false, (isAbyssal and "melvorItA:Abyssal" or nil)))
end
end
break
end
end
end
end
end
end


-- Artisan skills
local upgradeCat = false
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
for catName, parts in pairs(upgradePart) do
local skillData = SkillData[localSkillID]
if not Shared.tableIsEmpty(parts) then
local skill = skillData.name
if not upgradeCat then
for i, recipe in ipairs(skillData.recipes) do
table.insert(categoryArray, '[[Category:Upgraded Items]]')
if recipe.productID == item.id or
upgradeCat = true
(localSkillID == 'Cooking' and recipe.perfectCookID == item.id) or
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, item.id)) then
local level, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe)
table.insert(lineArray, Icons._SkillReq(skill, level, false, (isAbyssal and "melvorItA:Abyssal" or nil)))
break
end
end
local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading'
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, sep))
end
end
end
end


-- Township trading
--Next: Can we take it from somebody else -without- killing them?
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
local thiefItems = Skills.getThievingSourcesForItem(item.id)
local found = false
if type(thiefItems) == 'table' then
for j, tradeDef in ipairs(tsResource.items) do
local includedNPCs = {}
if tradeDef.itemID == item.id then
local thiefPart = {}
found = true
for i, thiefRow in ipairs(thiefItems) do
local levelReq = nil
if thiefRow.npc == 'all' then
if tradeDef.unlockRequirements ~= nil then
--if 'all' is the npc, this is a rare item so just say 'Thieving level 1'
for k, req in ipairs(tradeDef.unlockRequirements) do
table.insert(lineArray, Icons._SkillReq('Thieving', 1))
if req.type == 'SkillLevel' and req.skillID == 'melvorD:Township' then
elseif not Shared.contains(includedNPCs, thiefRow.npc) then
levelReq = req.level
table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
break
table.insert(includedNPCs, thiefRow.npc)
end
end
if levelReq == nil then
table.insert(lineArray, Icons.Icon({SkillData.Township.name, type='skill'}))
else
table.insert(lineArray, Icons._SkillReq(SkillData.Township.name, levelReq))
end
end
end
if found then
break
end
end
end
end
if found then
if not Shared.tableIsEmpty(thiefPart) then
break
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, sep))
end
end
end
-- Can we get this item by casting an Alt. Magic spell?
local castPart = {}
for i, spell in ipairs(Magic.getSpellsProducingItem(item.id)) do
table.insert(castPart, Icons.Icon({spell.name, type=Magic._getSpellIconType(spell), notext=true}))
end
if not Shared.tableIsEmpty(castPart) then
table.insert(lineArray, 'Casting: ' .. table.concat(castPart, sep))
end
end


-- Archaeology sources
--Check if we can make it ourselves
-- Digsites
local skillIDs = {
for i, digsite in ipairs(SkillData.Archaeology.digSites) do
['Gathering'] = {
local found = false
['Woodcutting'] = { recipeKey = 'trees' },
for artefactType, artefactItems in pairs(digsite.artefacts) do
['Fishing'] = { recipeKey = 'fish' },
for j, itemDef in ipairs(artefactItems) do
['Firemaking'] = { recipeKey = 'logs' },
if itemDef.itemID == item.id then
['Mining'] = { recipeKey = 'rockData' },
table.insert(lineArray, Icons._SkillReq(SkillData.Archaeology.name, digsite.level))
['Farming'] = { recipeKey = 'recipes' },
found = true
['Harvesting'] = { recipeKey = 'veinData' }
break
},
end
['Artisan'] = {
end
['Cooking'] = { },
if found then
['Smithing'] = { },
break
['Fletching'] = { },
end
['Crafting'] = { },
end
['Runecrafting'] = { },
if found then
['Herblore'] = { },
break
['Summoning'] = { }
end
}
end
}
-- Museum rewards
 
for i, museumReward in ipairs(SkillData.Archaeology.museumRewards) do
-- Gathering skills
if type(museumReward.items) == 'table' and Shared.contains(museumReward.items, item.id) then
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
table.insert(lineArray, Icons.Icon('Museum'))
local skillData = SkillData[localSkillID]
break
local skill = skillData.name
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
local hasProduct = doesRecipeHaveItemID(recipe, item.id)
if hasProduct then
if localSkillID == 'Farming' and recipe.seedCost ~= nil then
local seedItem = Items.getItemByID(recipe.seedCost.id)
if seedItem ~= nil then
table.insert(lineArray, 'Growing: ' .. Icons.Icon({seedItem.name, type='item', notext='true'}))
end
else
local level, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe)
table.insert(lineArray, Icons._SkillReq(skill, level, false, (isAbyssal and "melvorItA:Abyssal" or nil)))
end
break
end
end
end
end
end


-- Cartography
-- Artisan skills
-- Paper
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
for i, recipe in ipairs(SkillData.Cartography.paperRecipes) do
local skillData = SkillData[localSkillID]
if recipe.productId == item.id then
local skill = skillData.name
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
for i, recipe in ipairs(skillData.recipes) do
break
if recipe.productID == item.id or
(localSkillID == 'Cooking' and recipe.perfectCookID == item.id) or
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, item.id)) then
local level, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe)
table.insert(lineArray, Icons._SkillReq(skill, level, false, (isAbyssal and "melvorItA:Abyssal" or nil)))
break
end
end
end
end
end
-- POI discovery rewards
 
for i, worldMap in ipairs(SkillData.Cartography.worldMaps) do
-- Township trading
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
local found = false
local found = false
for j, poi in ipairs(worldMap.pointsOfInterest) do
for j, tradeDef in ipairs(tsResource.items) do
if type(poi.discoveryRewards) == 'table' and type(poi.discoveryRewards.items) == 'table' then
if tradeDef.itemID == item.id then
for k, itemDef in ipairs(poi.discoveryRewards.items) do
found = true
if itemDef.id == item.id then
local levelReq = nil
-- Find level for POI hex
if tradeDef.unlockRequirements ~= nil then
local level = 1
for k, req in ipairs(tradeDef.unlockRequirements) do
local poiHex = nil
if req.type == 'SkillLevel' and req.skillID == 'melvorD:Township' then
local skillID = SkillData.Cartography.skillID
levelReq = req.level
for m, hex in ipairs(worldMap.hexes) do
break
if hex.coordinates.q == poi.coords.q and hex.coordinates.r == poi.coords.r then
for n, req in ipairs(hex.requirements) do
if req.type == 'SkillLevel' and req.skillID == skillID then
level = req.level
break
end
end
break
end
end
end
table.insert(lineArray, Icons._SkillReq(SkillData.Cartography.name, level))
found = true
break
end
end
end
if levelReq == nil then
if found then
table.insert(lineArray, Icons.Icon({SkillData.Township.name, type='skill'}))
break
else
table.insert(lineArray, Icons._SkillReq(SkillData.Township.name, levelReq))
end
end
end
end
if found then
break
end
end
end
end
Line 831: Line 905:
end
end
end
end
-- Travel events
 
for i, event in ipairs(SkillData.Cartography.travelEvents) do
-- Archaeology sources
-- Digsites
for i, digsite in ipairs(SkillData.Archaeology.digSites) do
local found = false
local found = false
if type(event.rewards) == 'table' and type(event.rewards.items) == 'table' then
for artefactType, artefactItems in pairs(digsite.artefacts) do
for j, itemDef in ipairs(event.rewards.items) do
for j, itemDef in ipairs(artefactItems) do
if itemDef.id == item.id and itemDef.quantity > 0 then
if itemDef.itemID == item.id then
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
table.insert(lineArray, Icons._SkillReq(SkillData.Archaeology.name, digsite.level))
found = true
found = true
break
break
Line 845: Line 921:
break
break
end
end
end
if found then
break
end
end
end
end
 
-- Museum rewards
--AstrologyCheck
for i, museumReward in ipairs(SkillData.Archaeology.museumRewards) do
for i, dustDrop in ipairs(SkillData.Astrology.baseRandomItemChances) do
if type(museumReward.items) == 'table' and Shared.contains(museumReward.items, item.id) then
if dustDrop.itemID == item.id then
table.insert(lineArray, Icons.Icon('Museum'))
table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'}))
break
end
end
end
end


-- Woodcutting
-- Cartography
-- Raven Nest
-- Paper
if item.id == SkillData.Woodcutting.ravenNestItemID then
for i, recipe in ipairs(SkillData.Cartography.paperRecipes) do
local levelReq = nil
if recipe.productId == item.id then
for i, tree in ipairs(SkillData.Woodcutting.trees) do
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
if tree.canDropRavenNest and (levelReq == nil or tree.level < levelReq) then
break
levelReq = tree.level
end
end
end
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, levelReq))
end
-- Bird Nest, Ash, and Mushroom
-- POI discovery rewards
elseif Shared.contains({
for i, worldMap in ipairs(SkillData.Cartography.worldMaps) do
SkillData.Woodcutting.nestItemID,
local found = false
SkillData.Woodcutting.ashItemID,
for j, poi in ipairs(worldMap.pointsOfInterest) do
SkillData.Woodcutting.mushroomItemID
if type(poi.discoveryRewards) == 'table' and type(poi.discoveryRewards.items) == 'table' then
}, item.id) then
for k, itemDef in ipairs(poi.discoveryRewards.items) do
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, 1))
if itemDef.id == item.id then
end
-- Find level for POI hex
 
local level = 1
-- Fishing
local poiHex = nil
-- Junk
local skillID = SkillData.Cartography.skillID
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
for m, hex in ipairs(worldMap.hexes) do
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
if hex.coordinates.q == poi.coords.q and hex.coordinates.r == poi.coords.r then
elseif item.id == SkillData.Fishing.lostChestItem then
for n, req in ipairs(hex.requirements) do
table.insert(lineArray, Icons._SkillReq(SkillData.Fishing.name, 100))
if req.type == 'SkillLevel' and req.skillID == skillID then
end
level = req.level
-- Specials
break
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do
end
if GameData.getEntityByProperty(specialItem.drops, 'itemID', item.id) ~= nil then
end
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]')
break
end
end
table.insert(lineArray, Icons._SkillReq(SkillData.Cartography.name, level))
found = true
break
end
end
if found then
break
end
end
end
if found then
break
end
end
end
end
 
-- Travel events
-- Firemaking: Coal
for i, event in ipairs(SkillData.Cartography.travelEvents) do
if Shared.contains({SkillData.Firemaking.coalItemID,
local found = false
SkillData.Firemaking.ashItemID,
if type(event.rewards) == 'table' and type(event.rewards.items) == 'table' then
SkillData.Firemaking.charcoalItemID,
for j, itemDef in ipairs(event.rewards.items) do
SkillData.Firemaking.fireSpiritItemID,
if itemDef.id == item.id and itemDef.quantity > 0 then
SkillData.Firemaking.diamondItemID
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
}, item.id) then
found = true
table.insert(lineArray, Icons._SkillReq(SkillData.Firemaking.name, 1))
break
end
end
if found then
break
end
end
end
end


-- Mining: Gems
--AstrologyCheck
if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or
for i, dustDrop in ipairs(SkillData.Astrology.baseRandomItemChances) do
GameData.getEntityByProperty('randomSuperiorGems', 'itemID', item.id) ~= nil or
if dustDrop.itemID == item.id then
GameData.getEntityByProperty('randomAbyssalGems', 'itemID', item.id) ~= nil) then
table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'}))
table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
elseif item.id == SkillData.Mining.runestoneItemID then
-- From pure essence mining
local recipe = GameData.getEntityByID(SkillData.Mining.rockData, 'melvorTotH:Pure_Essence')
if recipe ~= nil then
table.insert(lineArray, Icons._SkillReq(SkillData.Mining.name, recipe.level))
end
end
end
end


-- General rare drops for non-combat skills
-- Woodcutting
-- Includes items like Circlet/Jewel of Rhaelyx, Mysterious stones, Signet ring half (a),
-- Raven Nest
-- relics (for Ancient Relics mode)
if item.id == SkillData.Woodcutting.ravenNestItemID then
local skillIconList, subText = {}, ''
local levelReq = nil
for i, skillDataAll in ipairs(GameData.rawData.skillData) do
for i, tree in ipairs(SkillData.Woodcutting.trees) do
local skillData = skillDataAll.data
if tree.canDropRavenNest and (levelReq == nil or tree.level < levelReq) then
local skillName, displaySkillName = skillData.name, false
levelReq = tree.level
-- All general rare drops within the Magic are for Alt. Magic
end
if skillDataAll.skillID == 'melvorD:Magic' then
skillName, displaySkillName = 'Alt. Magic', true
end
end
if type(skillData.rareDrops) == 'table' then
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, levelReq))
for j, rareDrop in ipairs(skillData.rareDrops) do
-- Bird Nest, Ash, and Mushroom
local isAltItem = (rareDrop.altItemID ~= nil and rareDrop.altItemID == item.id)
elseif Shared.contains({
if isAltItem or rareDrop.itemID == item.id then
SkillData.Woodcutting.nestItemID,
if Shared.tableIsEmpty(skillIconList) then
SkillData.Woodcutting.ashItemID,
-- Initialize subText
SkillData.Woodcutting.mushroomItemID
if isAltItem then
}, item.id) then
local wornItem = Items.getItemByID(rareDrop.itemID)
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, 1))
subText = ' while wearing ' .. Icons.Icon({wornItem.name, type='item'})
end
elseif rareDrop.altItemID ~= nil then
 
-- There exists an alt item, but we are not searching for it
-- Fishing
local altItem = Items.getItemByID(rareDrop.altItemID)
-- Junk
subText = ' if not worn (Instead of ' .. Icons.Icon({altItem.name, type='item'}) .. ')'
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
elseif rareDrop.itemID == 'melvorD:Mysterious_Stone' then
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
local foundItem = Items.getItemByID('melvorD:Crown_of_Rhaelyx')
elseif item.id == SkillData.Fishing.lostChestItem then
subText = '<br>after finding ' .. Icons.Icon({foundItem.name, type='item'})
table.insert(lineArray, Icons._SkillReq(SkillData.Fishing.name, 100))
end
end
if type(rareDrop.gamemodes) == 'table' then
-- Specials
local gamemodeText = {}
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do
for k, gamemodeID in ipairs(rareDrop.gamemodes) do
if GameData.getEntityByProperty(specialItem.drops, 'itemID', item.id) ~= nil then
local gamemode = GameData.getEntityByID('gamemodes', gamemodeID)
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]')
if gamemode ~= nil then
end
table.insert(gamemodeText, gamemode.name)
end
end
 
end
-- Firemaking: Coal
if not Shared.tableIsEmpty(gamemodeText) then
if Shared.contains({SkillData.Firemaking.coalItemID,
subText = subText .. ' (' .. table.concat(gamemodeText, ', ') .. ' only)'
SkillData.Firemaking.ashItemID,
end
SkillData.Firemaking.charcoalItemID,
end
SkillData.Firemaking.fireSpiritItemID,
end
SkillData.Firemaking.diamondItemID
local skillText = Icons.Icon({skillName, type='skill', notext=true})
}, item.id) then
if displaySkillName then
table.insert(lineArray, Icons._SkillReq(SkillData.Firemaking.name, 1))
skillText = skillText .. ' (' .. Icons.Icon({skillName, type='skill', noicon=true}) .. ')'
end
table.insert(skillIconList, skillText)
end
end
end
end
if not Shared.tableIsEmpty(skillIconList) then
table.insert(lineArray, 'Any action in: ' .. table.concat(skillIconList, ', ') .. subText)
skillIconList, subText = {}, ''
end
end


-- Supplementary stuff on top of general rare drops
-- Mining: Gems
if item.id == 'melvorD:Gold_Topaz_Ring' then
if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
GameData.getEntityByProperty('randomSuperiorGems', 'itemID', item.id) ~= nil or
elseif item.id == 'melvorD:Signet_Ring_Half_B' then
GameData.getEntityByProperty('randomAbyssalGems', 'itemID', item.id) ~= nil) then
table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
elseif item.id == 'melvorTotH:Deadly_Toxins_Potion' then
elseif item.id == SkillData.Mining.runestoneItemID then
--Adding a special override for Deadly Toxins potions
-- From pure essence mining
table.insert(lineArray, 'Brewing [[Lethal Toxins Potion]]s while wearing '..Icons.Icon({'Toxic Maker Gloves', type='item'}))
local recipe = GameData.getEntityByID(SkillData.Mining.rockData, 'melvorTotH:Pure_Essence')
if recipe ~= nil then
table.insert(lineArray, Icons._SkillReq(SkillData.Mining.name, recipe.level))
end
end
end


--Tokens are from the appropriate skill
-- General rare drops for non-combat skills
if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then
-- Includes items like Circlet/Jewel of Rhaelyx, Mysterious stones, Signet ring half (a),
for localSkillID, skillData in pairs(SkillData) do
-- relics (for Ancient Relics mode)
if skillData.masteryTokenID ~= nil and skillData.masteryTokenID == item.id then
local skillIconList, subText = {}, ''
table.insert(lineArray, Icons._SkillReq(skillData.name, 1))
for i, skillDataAll in ipairs(GameData.rawData.skillData) do
break
local skillData = skillDataAll.data
end
local skillName, displaySkillName = skillData.name, false
-- All general rare drops within the Magic are for Alt. Magic
if skillDataAll.skillID == 'melvorD:Magic' then
skillName, displaySkillName = 'Alt. Magic', true
end
end
end
if type(skillData.rareDrops) == 'table' then
 
for j, rareDrop in ipairs(skillData.rareDrops) do
-- Golbin Raid exclusive items
local isAltItem = (rareDrop.altItemID ~= nil and rareDrop.altItemID == item.id)
if item.golbinRaidExclusive then
if isAltItem or rareDrop.itemID == item.id then
table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'}))
if Shared.tableIsEmpty(skillIconList) then
end
-- Initialize subText
 
if isAltItem then
--Shop items (including special items like gloves that aren't otherwise listed)
local wornItem = Items.getItemByID(rareDrop.itemID)
if not Shared.tableIsEmpty(Shop.getItemSourceArray(item.id)) then
subText = ' while wearing ' .. Icons.Icon({wornItem.name, type='item'})
table.insert(lineArray, Icons.Icon({'Shop'}))
elseif rareDrop.altItemID ~= nil then
end
-- There exists an alt item, but we are not searching for it
 
local altItem = Items.getItemByID(rareDrop.altItemID)
--Easter Eggs (manual list 'cause don't have a better way to do that)
subText = ' if not worn (Instead of ' .. Icons.Icon({altItem.name, type='item'}) .. ')'
if Shared.contains(Items.EasterEggs, item.name) then
elseif rareDrop.itemID == 'melvorD:Mysterious_Stone' then
table.insert(lineArray, '[[Easter Eggs]]')
local foundItem = Items.getItemByID('melvorD:Crown_of_Rhaelyx')
end
subText = '<br>after finding ' .. Icons.Icon({foundItem.name, type='item'})
-- Event exclusive items (also a manual list)
end
if Shared.contains(Items.EventItems, item.name) then
if type(rareDrop.gamemodes) == 'table' then
table.insert(lineArray, '[[Events]]')
local gamemodeText = {}
end
for k, gamemodeID in ipairs(rareDrop.gamemodes) do
 
local gamemode = GameData.getEntityByID('gamemodes', gamemodeID)
-- Township Task reward
if gamemode ~= nil then
for _, task in ipairs(SkillData.Township.tasks) do
table.insert(gamemodeText, gamemode.name)
if task.rewards.items[1] ~= nil then -- Skip tasks with no items
end
if GameData.getEntityByID(task.rewards.items, item.id) then
end
table.insert(lineArray, Icons.Icon({'Tasks', type='township'}))
if not Shared.tableIsEmpty(gamemodeText) then
break
subText = subText .. ' (' .. table.concat(gamemodeText, ', ') .. ' only)'
end
end
end
local skillText = Icons.Icon({skillName, type='skill', notext=true})
if displaySkillName then
skillText = skillText .. ' (' .. Icons.Icon({skillName, type='skill', noicon=true}) .. ')'
end
table.insert(skillIconList, skillText)
end
end
end
end
end
end
if not Shared.tableIsEmpty(skillIconList) then
table.insert(lineArray, 'Any action in: ' .. table.concat(skillIconList, ', ') .. subText)
skillIconList, subText = {}, ''
end
end


local resultPart = {}
-- Supplementary stuff on top of general rare drops
if asList then
if item.id == 'melvorD:Gold_Topaz_Ring' then
table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
else
elseif item.id == 'melvorD:Signet_Ring_Half_B' then
table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br>") .. '</div>')
table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
elseif item.id == 'melvorTotH:Deadly_Toxins_Potion' then
--Adding a special override for Deadly Toxins potions
table.insert(lineArray, 'Brewing [[Lethal Toxins Potion]]s while wearing '..Icons.Icon({'Toxic Maker Gloves', type='item'}))
end
end
if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
return table.concat(resultPart)
end


function p.getItemSources(frame)
--Tokens are from the appropriate skill
local itemName = frame.args ~= nil and frame.args[1] or frame
if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then
local item = Items.getItem(itemName)
for localSkillID, skillData in pairs(SkillData) do
local asList = false
if skillData.masteryTokenID ~= nil and skillData.masteryTokenID == item.id then
local addCategories = false
table.insert(lineArray, Icons._SkillReq(skillData.name, 1))
if frame.args ~= nil then
break
asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
end
addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
end
end
end
if item == nil then
 
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
-- Golbin Raid exclusive items
if item.golbinRaidExclusive then
table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'}))
end
end


return p._getItemSources(item, asList, addCategories)
--Shop items (including special items like gloves that aren't otherwise listed)
end
if not Shared.tableIsEmpty(Shop.getItemSourceArray(item.id)) then
table.insert(lineArray, Icons.Icon({'Shop'}))
end


function p._getItemLootSourceTable(item)
--Easter Eggs (manual list 'cause don't have a better way to do that)
local resultPart = {}
if Shared.contains(Items.EasterEggs, item.name) then
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(lineArray, '[[Easter Eggs]]')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
end
table.insert(resultPart, '\r\n!Source!!Level!!Quantity!!colspan="2"|Chance')
-- Event exclusive items (also a manual list)
 
if Shared.contains(Items.EventItems, item.name) then
--Set up function for adding rows
table.insert(lineArray, '[[Events]]')
local buildRow = function(source, level, levelNum, minqty, qty, weight, totalWeight, expIcon)
end
if minqty == nil then minqty = 1 end
 
if expIcon == nil then expIcon = '' end
-- Township Task reward
if level == nil then level = 'N/A' end
for _, task in ipairs(SkillData.Township.tasks) do
local rowPart = {}
if task.rewards.items[1] ~= nil then -- Skip tasks with no items
table.insert(rowPart, '\r\n|-')
if GameData.getEntityByID(task.rewards.items, item.id) then
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
table.insert(lineArray, Icons.Icon({'Tasks', type='township'}))
-- Retrieve numeric level value for sorting, or remove anything between [[]]
break
local levelValue = ''
end
if levelNum ~= nil then
levelValue = tostring(levelNum)
else
levelValue = level:match('%[%[.-%]%]%s*(%w+)$') or ''
end
end
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon.. level)
end
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..Num.formatnum(minqty))
if qty ~= minqty then table.insert(rowPart, ' - '..Num.formatnum(qty)) end
local chance = weight / totalWeight * 100
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
local fmt = (chance < 0.10 and '%.2g') or '%.2f'
local chanceStr = string.format(fmt, chance)
if weight >= totalWeight then
-- Fraction would be 1/1, so only show the percentage
chanceStr = '100'
table.insert(rowPart, '\r\n|colspan="2" ')
else
local fraction = Num.fraction(weight, totalWeight)
if Shared.contains(fraction, '%.') then
--If fraction contains decimals, something screwy happened so just show only percentage
--(happens sometimes with the rare thieving items)
table.insert(rowPart, '\r\n|colspan="2" ')
else
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chanceStr .. '"| ' .. Num.fraction(weight, totalWeight) .. '\r\n|')
end
end
if weight == -1 then
--Weight of -1 means this is a weird row that has a variable percentage
table.insert(rowPart, 'style="text-align: right;" data-sort-value="0"|Varies (see Thieving page)')
else
table.insert(rowPart, 'style="text-align: right;" data-sort-value="'.. chanceStr .. '"|'..chanceStr..'%')
end
return table.concat(rowPart)
end
local dropRows = {}


--Alright, time to go through a few ways to get the item
local resultPart = {}
--First up: Can we kill somebody and take theirs?
if asList then
for i, drop in ipairs(p._getItemMonsterSources(item)) do
table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
local monster = GameData.getEntityByID('monsters', drop.id)
else
local iconName = monster.name
table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br>") .. '</div>')
if SourceOverrides[drop.id] ~= nil then
end
iconName = SourceOverrides[drop.id]
if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
end
return table.concat(resultPart)
end


if monster ~= nil then
function p.getItemSources(frame)
local monsterLevel = Monsters._getMonsterCombatLevel(monster)
local itemName = frame.args ~= nil and frame.args[1] or frame
table.insert(dropRows, {
local item = Items.getItem(itemName)
source = Icons.Icon({iconName, type='monster'}),
local asList = false
level = Icons.Icon({'Combat', 'Monsters', notext=true}) .. ' Level ' .. Num.formatnum(monsterLevel),
local addCategories = false
levelNum = monsterLevel,
if frame.args ~= nil then
minqty = drop.minQty,
asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
qty = drop.maxQty,
addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
weight = drop.dropWt,
totalWeight = drop.totalWt,
expIcon = Icons.getExpansionIcon(drop.id)})
end
end
end
if item == nil then
--Patching in here because it uses the same format
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
--Can we find this in an Archaeology digsite?
for i, drop in ipairs(p._getItemArchSources(item)) do
if drop.name ~= nil then
table.insert(dropRows, {
source = Icons.Icon({drop.name, type='poi'}),
level = Icons._SkillReq('Archaeology', drop.level) .. ' ('..drop.size..')',
levelNum = drop.level,
minqty = drop.minQty,
qty = drop.maxQty,
weight = drop.dropWt,
totalWeight = drop.totalWt,
expIcon = Icons.getExpansionIcon(drop.id)})
end
end
end


-- Is the item dropped from any dungeon?
return p._getItemSources(item, asList, addCategories)
local dungeonEntities = {
end
['Dungeon'] = GameData.rawData.dungeons,
 
['The Abyss'] = GameData.rawData.abyssDepths
function p._getItemLootSourceTable(item)
}
local resultPart = {}
for entity, dungeons in pairs(dungeonEntities) do
table.insert(resultPart, '{| class="wikitable sortable stickyHeader col-3-center col-4-center"')
for i, dungeon in ipairs(dungeons) do
table.insert(resultPart, '\r\n|- class="headerRow-0"')
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
table.insert(resultPart, '\r\n!Source!!Level!!Qty!!colspan="2"|Chance')
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
 
table.insert(dropRows, {
--Set up function for adding rows
source = Icons.Icon({dungeon.name, type='combatArea'}),
local buildRow = function(source, level, levelNum, minqty, qty, weight, totalWeight, expIcon)
level = '[['..entity..']]',
if minqty == nil then minqty = 1 end
minqty = 1,
if expIcon == nil then expIcon = '' end
qty = 1,  
if level == nil then level = 'N/A' end
weight = 1,
local rowPart = {}
totalWeight = 1,
table.insert(rowPart, '\r\n|-')
expIcon = Icons.getExpansionIcon(dungeon.id)})
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
elseif dungeon.eventID ~= nil then
-- Retrieve numeric level value for sorting, or remove anything between [[]]
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
local levelValue = ''
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
if levelNum ~= nil then
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
levelValue = tostring(levelNum)
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
else
if item.id == itemRewardID then
levelValue = level:match('%[%[.-%]%]%s*(%w+)$') or ''
local sourceTxt = Icons.Icon({dungeon.name, type='combatArea'}) .. (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or ', Cycle ' .. eventCycle)
end
table.insert(dropRows, {
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon..' '..level)
source = sourceTxt,  
table.insert(rowPart, '\r\n|data-sort-value="'..qty..'"|'..Num.formatnum(minqty))
level = '[['..entity..']]',
if qty ~= minqty then table.insert(rowPart, ' - '..Num.formatnum(qty)) end
minqty = 1,
local chance = weight / totalWeight * 100
qty = 1,
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
weight = 1,  
local fmt = (chance < 0.10 and '%.2g') or '%.2f'
totalWeight = 1})
local chanceStr = string.format(fmt, chance)
break
if weight >= totalWeight then
end
-- Fraction would be 1/1, so only show the percentage
end
chanceStr = '100'
end
table.insert(rowPart, '\r\n|colspan="2" ')
else
local fraction = Num.fraction(weight, totalWeight)
if Shared.contains(fraction, '%.') then
--If fraction contains decimals, something screwy happened so just show only percentage
--(happens sometimes with the rare thieving items)
table.insert(rowPart, '\r\n|colspan="2" ')
else
table.insert(rowPart, '\r\n|data-sort-value="' .. chanceStr .. '"| ' .. Num.fraction(weight, totalWeight) .. '\r\n|')
end
end
end
end
if weight == -1 then
--Weight of -1 means this is a weird row that has a variable percentage
table.insert(rowPart, 'data-sort-value="0"|Varies (see Thieving page)')
else
table.insert(rowPart, 'data-sort-value="'.. chanceStr .. '"|'..chanceStr..'%')
end
return table.concat(rowPart)
end
end
local dropRows = {}


for i, stronghold in ipairs(GameData.rawData.strongholds) do
--Alright, time to go through a few ways to get the item
for tier, tierData in pairs(stronghold.tiers) do
--First up: Can we kill somebody and take theirs?
if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
for i, drop in ipairs(p._getItemMonsterSources(item)) do
for i, reward in ipairs(tierData.rewards.items) do
local monster = GameData.getEntityByID('monsters', drop.id)
if reward.id == item.id then
local iconName = monster.name
table.insert(dropRows, {
if SourceOverrides[drop.id] ~= nil then
source = Icons.Icon({stronghold.name, type='combatArea'}),
iconName = SourceOverrides[drop.id]
level = '[[Strongholds|'..tier..']]',
minqty = 1,
qty = 1,
weight = tierData.rewards.chance,
totalWeight = 100,
expIcon = Icons.getExpansionIcon(stronghold.id)})
end
end
end
end
end
end


-- Can we find it in an openable item?
if monster ~= nil then
for i, item2 in ipairs(GameData.rawData.items) do
local monsterLevel = Monsters._getMonsterCombatLevel(monster)
if item2.dropTable ~= nil then
table.insert(dropRows, {
local minQty, maxQty, wt, totalWt = 1, 1, 0, 0
source = Icons.Icon({iconName, type='monster'}),  
for j, loot in ipairs(item2.dropTable) do
level = Icons.Icon({'Combat', 'Monsters', notext=true}) .. ' Level ' .. Num.formatnum(monsterLevel),
totalWt = totalWt + loot.weight
levelNum = monsterLevel,
if loot.itemID == item.id then
minqty = drop.minQty,
wt = loot.weight
qty = drop.maxQty,
minQty = loot.minQuantity
weight = drop.dropWt,
maxQty = loot.maxQuantity
totalWeight = drop.totalWt,
end
expIcon = Icons.getExpansionIcon(drop.id)})
end
end
end


if wt > 0 then
-- Is the item dropped from any dungeon?
local sourceTxt = Icons.Icon({item2.name, type='item'})
local dungeonEntities = {
table.insert(dropRows, {
['Dungeon'] = GameData.rawData.dungeons,
source = sourceTxt,  
['The Abyss'] = GameData.rawData.abyssDepths
level = '[[Chest]]',
}
minqty = minQty,  
for entity, dungeons in pairs(dungeonEntities) do
qty = maxQty,  
for i, dungeon in ipairs(dungeons) do
weight = wt,  
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
totalWeight = totalWt,  
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
expIcon = Icons.getExpansionIcon(item2.id)})
table.insert(dropRows, {
end
source = Icons.Icon({dungeon.name, type='combatArea'}),  
end
level = '[['..entity..']]',
end
minqty = 1,  
 
qty = 1,  
-- Can it be obtained from Thieving?
weight = 1,  
local thiefItems = Skills.getThievingSourcesForItem(item.id)
totalWeight = 1,  
for i, thiefRow in ipairs(thiefItems) do
expIcon = Icons.getExpansionIcon(dungeon.id)})
local sourceTxt = ''
elseif dungeon.eventID ~= nil then
if thiefRow.npc == 'all' then
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
sourceTxt = 'Thieving Rare Drop'
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
else
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
if item.id == itemRewardID then
local sourceTxt = Icons.Icon({dungeon.name, type='combatArea'}) .. (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or ', Cycle ' .. eventCycle)
table.insert(dropRows, {
source = sourceTxt,
level = '[['..entity..']]',
minqty = 1,
qty = 1,
weight = 1,  
totalWeight = 1})
break
end
end
end
end
end
end
local levelNum = thiefRow.abyssalLevel or thiefRow.level
local isAbyssal = thiefRow.abyssalLevel ~= nil
table.insert(dropRows, {
source = sourceTxt,
level = Icons._SkillReq("Thieving", levelNum, false, (isAbyssal and "melvorItA:Abyssal" or nil)),
levelNum = levelNum,
minqty = thiefRow.minQty,
qty = thiefRow.maxQty,
weight = thiefRow.wt,
totalWeight = thiefRow.totalWt,
expIcon = Icons.getExpansionIcon(thiefRow.npcID)})
end
end


-- Fishing: Junk & Specials
for i, stronghold in ipairs(GameData.rawData.strongholds) do
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
for tier, tierData in pairs(stronghold.tiers) do
local fishSource = '[[Fishing#Junk|Junk]]'
if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
local fishType = Icons.Icon({'Fishing', type='skill'})
for i, reward in ipairs(tierData.rewards.items) do
local fishTotWeight = Shared.tableCount(SkillData.Fishing.junkItemIDs)
if reward.id == item.id then
table.insert(dropRows, {
table.insert(dropRows, {
source = fishSource,
source = Icons.Icon({stronghold.name, type='combatArea'}),  
level = Icons._SkillReq("Fishing", 1),
level = '[[Strongholds|'..tier..']]',
levelNum = 1,
minqty = 1,  
minqty = 1,  
qty = 1,  
qty = 1,  
weight = tierData.rewards.chance,  
weight = 1,  
totalWeight = 100,
totalWeight = fishTotWeight})
expIcon = Icons.getExpansionIcon(stronghold.id)})
else
end
local fishTotWeight, fishItem, realmID = {['melvorD:Melvor'] = 0, ['melvorItA:Abyssal'] = 0}, nil, nil
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do
for f, drop in ipairs(specialItem.drops) do
if drop.itemID == item.id then
fishItem = drop
realmID = specialItem.realmID
end
end
fishTotWeight[specialItem.realmID] = fishTotWeight[specialItem.realmID] + drop.weight
end
end
end
end
if fishItem ~= nil then
end
local fishSource = '[[Fishing#Special|Special]]'
 
local fishType = Icons.Icon({SkillData.Fishing.name, type='skill'})
-- Can we find it in an openable item?
table.insert(dropRows, {
for i, item2 in ipairs(GameData.rawData.items) do
source = fishSource,
if item2.dropTable ~= nil then
level = Icons._SkillReq("Fishing", 1, false, realmID),
local minQty, maxQty, wt, totalWt = 1, 1, 0, 0
levelNum = 1,
for j, loot in ipairs(item2.dropTable) do
minqty = fishItem.minQuantity,
totalWt = totalWt + loot.weight
qty = fishItem.maxQuantity,
if loot.itemID == item.id then
weight = fishItem.weight,
wt = loot.weight
totalWeight = fishTotWeight[realmID]})
minQty = loot.minQuantity
end
maxQty = loot.maxQuantity
end
end
end


-- Mining: Gems, and also Alt. Magic spells producing random gems
if wt > 0 then
if Shared.contains({'Gem', 'Superior Gem', 'Abyssal Gem'}, item.type) then
local sourceTxt = Icons.Icon({item2.name, type='item'})
local gemKeys = { 'randomGems', 'randomSuperiorGems', 'randomAbyssalGems' }
table.insert(dropRows, {
for i, gemKey in ipairs(gemKeys) do
source = sourceTxt,  
local thisGem, totalGemWeight = nil, 0
level = '[[Chest]]',
for j, gem in ipairs(GameData.rawData[gemKey]) do
minqty = minQty,  
totalGemWeight = totalGemWeight + gem.weight
qty = maxQty,  
if gem.itemID == item.id then
weight = wt,  
thisGem = gem
totalWeight = totalWt,  
end
expIcon = Icons.getExpansionIcon(item2.id)})
end
end
if thisGem ~= nil then
end
local expIcon = ''
end
local sourceTxt
local lv = nil
if item.type == 'Superior Gem' then
expIcon = Icons.TotH()
sourceTxt = '[[Mining#Superior Gems|Superior Gem]]'
-- Superior gems can only be found with Mining 100 or above
lv = 100
else
sourceTxt = '[[Mining#Gems|Gem]]'
-- Gems can only be found with any Mining level
lv = 1
end
table.insert(dropRows, {
source = sourceTxt,
level = Icons._SkillReq('Mining', lv),
levelNum = lv,
minqty = thisGem.minQuantity,
qty = thisGem.maxQuantity,
weight = thisGem.weight,
totalWeight = totalGemWeight,
expIcon = expIcon})
-- Check for Alt. Magic spells also
local producesKey = (gemKey == 'randomGems' and 'RandomGem') or (gemKey == 'randomSuperiorGems' and 'RandomSuperiorGem') or nil
if producesKey ~= nil then
for j, spell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
if spell.produces ~= nil and spell.produces == producesKey then
table.insert(dropRows, {
source = Icons.Icon({spell.name, type=Magic._getSpellIconType(spell)}),
level = Icons.Icon({'Alternative Magic', type='skill', img='Magic', notext=true}) .. ' Level ' .. spell.level,
levelNum = spell.level,
minqty = thisGem.minQuantity,
qty = thisGem.maxQuantity,
weight = thisGem.weight,
totalWeight = totalGemWeight,
expIcon = Icons.getExpansionIcon(spell.id)})
end
end
end
end
end
end


--Make sure to return nothing if there are no drop sources
-- Can it be obtained from Thieving?
if Shared.tableIsEmpty(dropRows) then return '' end
local thiefItems = Skills.getThievingSourcesForItem(item.id)
for i, thiefRow in ipairs(thiefItems) do
table.sort(dropRows, function(a, b)
local sourceTxt = ''
if a.weight / a.totalWeight == b.weight / b.totalWeight then
if thiefRow.npc == 'all' then
if a.minqty + a.qty == b.minqty + b.qty then
sourceTxt = 'Thieving Rare Drop'
return (a.level == b.level and a.source < b.source) or a.level < b.level
else
else
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
return a.minqty + a.qty > b.minqty + b.qty
end
end
local levelNum = thiefRow.abyssalLevel or thiefRow.level
else
local isAbyssal = thiefRow.abyssalLevel ~= nil
return a.weight / a.totalWeight > b.weight / b.totalWeight
table.insert(dropRows, {
end
source = sourceTxt,
end)
level = Icons._SkillReq("Thieving", levelNum, false, (isAbyssal and "melvorItA:Abyssal" or nil)),
for i, data in ipairs(dropRows) do
levelNum = levelNum,
table.insert(resultPart, buildRow(data.source, data.level, data.levelNum, data.minqty, data.qty, data.weight, data.totalWeight, data.expIcon))
minqty = thiefRow.minQty,  
qty = thiefRow.maxQty,  
weight = thiefRow.wt,  
totalWeight = thiefRow.totalWt,  
expIcon = Icons.getExpansionIcon(thiefRow.npcID)})
end
end


table.insert(resultPart, '\r\n|}')
-- Fishing: Junk & Specials
return table.concat(resultPart)
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
end
local fishSource = '[[Fishing#Junk|Junk]]'
 
local fishType = Icons.Icon({'Fishing', type='skill'})
function p.getItemLootSourceTable(frame)
local fishTotWeight = Shared.tableCount(SkillData.Fishing.junkItemIDs)
local itemName = frame.args ~= nil and frame.args[1] or frame
table.insert(dropRows, {
local item = Items.getItem(itemName)
source = fishSource,
if item == nil then
level = Icons._SkillReq("Fishing", 1),
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
levelNum = 1,
end
minqty = 1,
 
qty = 1,
return p._getItemLootSourceTable(item)
weight = 1,
end
totalWeight = fishTotWeight})
 
else
function p._getItemUpgradeTable(item)
local fishTotWeight, fishItem, realmID = {['melvorD:Melvor'] = 0, ['melvorItA:Abyssal'] = 0}, nil, nil
local resultPart = {}
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do
local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id)
for f, drop in ipairs(specialItem.drops) do
if upgrade ~= nil then
if drop.itemID == item.id then
local upgradeCost = Common.getCostString({
fishItem = drop
["items"] = upgrade.itemCosts,
realmID = specialItem.realmID
["currencies"] = upgrade.currencyCosts
end
})
fishTotWeight[specialItem.realmID] = fishTotWeight[specialItem.realmID] + drop.weight
end
end
if fishItem ~= nil then
local fishSource = '[[Fishing#Special|Special]]'
local fishType = Icons.Icon({SkillData.Fishing.name, type='skill'})
table.insert(dropRows, {
source = fishSource,
level = Icons._SkillReq("Fishing", 1, false, realmID),
levelNum = 1,
minqty = fishItem.minQuantity,
qty = fishItem.maxQuantity,  
weight = fishItem.weight,
totalWeight = fishTotWeight[realmID]})
end
end


table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]')
--Make sure to return nothing if there are no drop sources
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
if Shared.tableIsEmpty(dropRows) then return '' end
table.insert(resultPart, upgradeCost)
table.insert(resultPart, '\r\n|}')
table.sort(dropRows, function(a, b)
end
if a.weight / a.totalWeight == b.weight / b.totalWeight then
if a.minqty + a.qty == b.minqty + b.qty then
return (a.level == b.level and a.source < b.source) or a.level < b.level
else
return a.minqty + a.qty > b.minqty + b.qty
end
else
return a.weight / a.totalWeight > b.weight / b.totalWeight
end
end)
for i, data in ipairs(dropRows) do
table.insert(resultPart, buildRow(data.source, data.level, data.levelNum, data.minqty, data.qty, data.weight, data.totalWeight, data.expIcon))
end
 
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.getItemUpgradeTable(frame)
function p.getItemLootSourceTable(frame)
local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
Line 1,389: Line 1,437:
end
end


return p._getItemUpgradeTable(item)
return p._getItemLootSourceTable(item)
end
end


function p._getSuperheatSmithRecipe(item)
function p._getItemUpgradeTableData(item, tableData)
local smithRecipe = GameData.getEntityByProperty(SkillData.Smithing.recipes, 'productID', item.id)
if tableData == nil then tableData = {} end
 
local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id)
if upgrade ~= nil then
local reqs = nil
if item.charges ~= nil and item.tier ~= nil then
local levelUnlock = GameData.getEntityByProperty(SkillData.Herblore.masteryLevelUnlocks, 'descriptionID', item.tier + 1)
if levelUnlock ~= nil then
reqs = Icons._MasteryReq(item.name, levelUnlock.level, false)
end
end
 
table.insert(tableData, {
['reqs'] = reqs,
['costs'] = Common.getCostString({ ["items"] = upgrade.itemCosts, ["currencies"] = upgrade.currencyCosts }),
['qty'] = (upgrade.quantity or 1),
['source'] = '[[Upgrading Items|Item ' .. (upgrade.isDowngrade and 'Downgrade' or 'Upgrade') ..']]'
})
end
 
return tableData
end
 
function p.getItemUpgradeTable(frame)
local itemName = frame.args ~= nil and frame.args[1] or frame
local item = Items.getItem(itemName)
if item == nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end
 
return p.buildCreationTable(p._getItemUpgradeTableData(item), item)
end
 
function p._getSuperheatSmithRecipe(item)
local smithRecipe = GameData.getEntityByProperty(SkillData.Smithing.recipes, 'productID', item.id)
if smithRecipe ~= nil and smithRecipe.categoryID == 'melvorD:Bars' then
if smithRecipe ~= nil and smithRecipe.categoryID == 'melvorD:Bars' then
return smithRecipe
return smithRecipe
Line 1,399: Line 1,481:
end
end


function p._getItemSuperheatTable(item)
function p._getItemSuperheatTableData(item, tableData)
--Manually build the Superheat Item table
if tableData == nil then tableData = {} end
 
-- Validate that the item can be superheated
-- Validate that the item can be superheated
local smithRecipe = p._getSuperheatSmithRecipe(item)
local smithRecipe = p._getSuperheatSmithRecipe(item)
Line 1,407: Line 1,490:
end
end


local oreStringPart, coalString = {}, ''
local oreStringPart, coalString, smithingReq = {}, '', Icons._SkillReq('Smithing', smithRecipe.level, false)
for i, mat in ipairs(smithRecipe.itemCosts) do
for i, mat in ipairs(smithRecipe.itemCosts) do
local matItem = Items.getItemByID(mat.id)
local matItem = Items.getItemByID(mat.id)
if mat.id == 'melvorD:Coal_Ore' then
if mat.id == 'melvorD:Coal_Ore' then
coalString = Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity})
coalString = Icons.Icon({ matItem.name, type='item', qty=mat.quantity })
else
else
table.insert(oreStringPart, Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity}))
table.insert(oreStringPart, Icons.Icon({ matItem.name, type='item', qty=mat.quantity }))
end
end
end
end
--Set up the header
local superheatTable = {}
table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
table.insert(superheatTable, '!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level')
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level')
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP')
table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
table.insert(superheatTable, '!!Ore!!Runes')


--Loop through all the variants
--Loop through all the variants
Line 1,431: Line 1,505:
if spell.specialCost ~= nil and Shared.contains({ 'BarIngredientsWithCoal', 'BarIngredientsWithoutCoal' }, spell.specialCost.type) then
if spell.specialCost ~= nil and Shared.contains({ 'BarIngredientsWithCoal', 'BarIngredientsWithoutCoal' }, spell.specialCost.type) then
local imgType = Magic._getSpellIconType(spell)
local imgType = Magic._getSpellIconType(spell)
table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type=imgType, notext=true, size=50}))
local costs = table.concat(oreStringPart, '<br>')
table.insert(superheatTable, '||'..Icons.Icon({spell.name, type=imgType, noicon=true})..'||style="text-align:right;"|'..smithRecipe.level)
 
table.insert(superheatTable, '||style="text-align:right;"|'..spell.level..'||style="text-align:right;"|'..spell.baseExperience)
table.insert(superheatTable, '||style="text-align:right;"|'..spell.productionRatio)
table.insert(superheatTable, '|| '..table.concat(oreStringPart, ', '))
if spell.specialCost.type == 'BarIngredientsWithCoal' and coalString ~= '' then
if spell.specialCost.type == 'BarIngredientsWithCoal' and coalString ~= '' then
table.insert(superheatTable, (not Shared.tableIsEmpty(oreStringPart) and ', ' or '') .. coalString)
costs = costs .. '<br>' .. coalString
end
end
table.insert(superheatTable, '||style="text-align:center"| ' .. Magic._getSpellRunes(spell))
end
end


--Add the table end and add the table to the result string
table.insert(tableData, {
table.insert(superheatTable, '\r\n|}')
['skill'] = 'Alt Magic',
return table.concat(superheatTable)
['lvl'] = spell.level,
['reqs'] = smithingReq .. '<br>' .. Icons.Icon({'Alt Magic', type='skill', notext=true}) .. ' Level ' .. spell.level,
['isAbyssal'] = false,
['xp'] = spell.baseExperience,
['costs'] = costs,
['qty'] = spell.productionRatio,
['source'] = Icons.Icon({ spell.name, type=imgType }),
['time'] = 2,
['runeCost'] = Magic._getSpellRunes(spell)
})
end
end
 
return tableData
end
end


Line 1,455: Line 1,536:
end
end


return p._getItemSuperheatTable(item)
return p.buildCreationTable(p._getItemSuperheatTableData(item), item)
end
end


function p._getTownshipTraderTable(item)
function p._getTownshipTraderTableData(item, tableData)
if tableData == nil then tableData = {} end
 
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
for j, tradeDef in ipairs(tsResource.items) do
for j, tradeDef in ipairs(tsResource.items) do
if tradeDef.itemID == item.id then
if tradeDef.itemID == item.id then
-- Item found, build table
-- Item found, insert the data
local res = GameData.getEntityByID(SkillData.Township.resources, tsResource.resourceID)
local res = GameData.getEntityByID(SkillData.Township.resources, tsResource.resourceID)
local resName = (res ~= nil and res.name) or 'Unknown'
local resName = (res ~= nil and res.name) or 'Unknown'
local resQty = math.max(item.sellsFor, 2)
local resQty = math.max(item.sellsFor, 2)
local townshipReq = GameData.getEntityByProperty(tradeDef.unlockRequirements, 'skillID', 'melvorD:Township')
local lvl = townshipReq.level + (townshipReq.type == 'AbyssalLevel' and 120 or 0)


local resultPart = {}
table.insert(tableData, {
table.insert(resultPart, '{| class="wikitable"\n|-')
['lvl'] = lvl,
table.insert(resultPart, '\n!colspan="2"| ' .. Icons.Icon({'Township', 'Trader', type='skill'}))
['reqs'] = Common.getRequirementString(tradeDef.unlockRequirements),
table.insert(resultPart, '\n|-\n!style="text-align:right;"| Cost')
['isAbyssal'] = namespace == 'melvorItA',
table.insert(resultPart, '\n| ' .. Icons.Icon({resName, qty=resQty, type='resource'}))
['costs'] = Icons.Icon({ resName, qty=resQty, type='resource' }),
table.insert(resultPart, '\n|-\n!style="text-align:right;| Requirements')
['qty'] = 1,
table.insert(resultPart, '\n| ' .. Shop.getRequirementString(tradeDef.unlockRequirements))
['source'] = Icons.Icon({ 'Township', 'Trader', type='skill' }),
table.insert(resultPart, '\n|}')
})
 
break
return table.concat(resultPart)
end
end
end
end
end
end
return ''
 
return tableData
end
end


function p._getItemSourceTables(item)
function p._getItemShopTableData(item, tableData)
local resultPart = {}
if tableData == nil then tableData = {} end
local shopTable = Shop._getItemShopTable(item)
 
if shopTable ~= '' then
local purchaseArray = Shop.getItemSourceArray(item.id)
table.insert(resultPart, '===Shop===\r\n'..shopTable)
end


local creationTable = p._getCreationTable(item)
for i, purchaseData in ipairs(purchaseArray) do
if creationTable ~= '' then
local purchase = purchaseData.purchase
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
local namespace, localID = Shared.getLocalID(purchase.id)
table.insert(resultPart, '===Creation===\r\n'..creationTable)
local source = nil
end
-- Show icon text when it's the only source of this item
local notext = (Shared.tableCount(tableData) + Shared.tableCount(purchaseArray) > 1)


local upgradeTable = p._getItemUpgradeTable(item)
if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
if upgradeTable ~= '' then
source = Shop._getPurchaseExpansionIcon(purchase) .. Common.getPurchaseIcon({purchase})
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
-- Always show icon text when there's multiple items
if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end
-- notext = false
table.insert(resultPart, upgradeTable)
else
end
source = Icons.Icon({'Shop'}) .. ' Purchase'
end


local townshipTable = p._getTownshipTraderTable(item)
table.insert(tableData, {
if townshipTable ~= '' then
['reqs'] = Common.getRequirementString(purchase.purchaseRequirements),
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\n') end
['isAbyssal'] = namespace == 'melvorItA',
table.insert(resultPart, '===Township===\n' .. townshipTable)
['costs'] = Common.getCostString(purchase.cost),
['qty'] = purchaseData.qty,
['contents'] = Shop._getPurchaseContents(purchase, true, notext),
['source'] = source,
['center'] = notext
})
end
end


return tableData
end
function p._getItemSourceTables(item)
local resultPart = {}
local sourceData = {}
-- Is the item Created or Produced by anything?
p._getCreationTableData(item, sourceData)
-- Can the item be obtained by upgrading?
p._getItemUpgradeTableData(item, sourceData)
-- Can the item be obtained by Superheating?
if p._getSuperheatSmithRecipe(item) ~= nil then
if p._getSuperheatSmithRecipe(item) ~= nil then
table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt. Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
p._getItemSuperheatTableData(item, sourceData)
end
-- Can the item be traded for in Township?
p._getTownshipTraderTableData(item, sourceData)
-- Can the item be purchased?
p._getItemShopTableData(item, sourceData)
 
local sourceTable = p.buildCreationTable(item, sourceData)
if sourceTable ~= '' then
table.insert(resultPart, '===Creation===\r\n' .. sourceTable)
end
end


Line 1,516: Line 1,626:
if lootTable ~= '' then
if lootTable ~= '' then
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
table.insert(resultPart, '===Loot===\r\n'..lootTable)
table.insert(resultPart, '===Loot===\r\n' .. lootTable)
end
end
return table.concat(resultPart)
return table.concat(resultPart)
end
end
Line 1,618: Line 1,729:
if found ~= nil then
if found ~= nil then
local min = found.minQuantity
local max = found.maxQuantity
table.insert(resultArray, {
table.insert(resultArray, {
id = digSite.id,  
id = digSite.id,  
Line 1,625: Line 1,734:
level = digSite.level,
level = digSite.level,
size = sizeName,  
size = sizeName,  
minQty = min,  
minQty = found.minQuantity,  
maxQty = max,  
maxQty = found.maxQuantity,  
dropWt = found.weight,  
dropWt = found.weight,  
totalWt = sizeWeight})
totalWt = sizeWeight
})
end
end
end
end
Line 1,646: Line 1,756:
function p.test()
function p.test()
local checkItems = {
local checkItems = {
"Circlet of Rhaelyx",
-- "Circlet of Rhaelyx",
"Jewel of Rhaelyx",
-- "Jewel of Rhaelyx",
"Signet Ring Half (a)",
-- "Signet Ring Half (a)",
"Signet Ring Half (b)",
-- "Signet Ring Half (b)",
"Astrology Lesser Relic",
-- "Astrology Lesser Relic",
"Mysterious Stone",
"Mysterious Stone",
"Charge Stone of Rhaelyx",
"Gold Topaz Ring",
"Gold Topaz Ring",
"Charcoal",
"Charcoal",
"Ash",
"Ash",
"Coal Ore",
"Coal Ore",
"Golden Star",
"Potion Box III",
"Rune Essence",
"Rune Essence",
"Gold Bar",
"Dragonite Bar",
"Rune Platebody",
"Holy Dust",
-- "Rune Platebody",
"Arrow Shafts",
"Arrow Shafts",
"Yew Longbow",
-- "Yew Longbow",
"Water Rune",
"Blood Rune",
"Steam Rune",
"Steam Rune",
"Wolf",
-- "Wolf",
"Fox",
"Fox",
"Leprechaun",
"Leprechaun",
"Void Wisp",
"Void Wisp",
"Redwood Logs",
-- "Redwood Logs",
"Shadow Raven Nest",
-- "Shadow Raven Nest",
"Raw Shrimp",
"Raw Shrimp",
"Shrimp",
"Shrimp",
"Carrot Cake",
"Carrot Cake",
"Carrot Cake (Perfect)",
-- "Carrot Cake (Perfect)",
"Mantalyme Herb",
-- "Mantalyme Herb",
"Carrot",
"Carrot",
"Controlled Heat Potion II",
"Controlled Heat Potion II",
"Topaz",
"Topaz",
"Oricha",
"Oricha",
"Nightopal",
"Nightopal",
"Sanguine Blade",
-- "Sanguine Blade",
"Ring of Power",
-- "Ring of Power",
"Infernal Claw",
-- "Infernal Claw",
"Chapeau Noir",
-- "Chapeau Noir",
"Stardust",
"Stardust",
"Golden Stardust",
"Golden Stardust",
"Abyssal Stardust",
"Abyssal Stardust",
"Rope",
"Rope",
"Ancient Ring of Mastery",
"Ancient Ring of Mastery",
"Mastery Token (Cooking)",
"Mastery Token (Cooking)",
"Thief's Moneysack",
-- "Slayer Deterer",
"Paper",
-- "Lemon",
"Aranite Brush",
"Charged Diamond Shard",
"Barrier Dust",
"Gloom Resin",
"Gloom Amber",
"Gloom Vine",
"Gloom Vein Seed",
"Elite Chest",
"Gem Gloves",
"Gem Gloves",
"Thief's Moneysack",
"Magic Bones",
"Golden Star",
"Bowstring",
"Slayer Deterer",
"Superior Max Skillcape",
"Paper",
"Abyssal Coin Contract II",
"Lemon",
"Dark Summon Consumable II",
"Aranite Brush",
"Abyssal Slayer Gear Upgrade Kit",
"Barrier Dust",
"Topaz Bolts (Enchanted)",
"Gloom Resin",
"Summoning Shard (Black)",
"Gloom Amber",
"Dragon Javelin",
"Gloom Vine",
"Skillers Body",
"Gloom Vein Seed",
"Abyssal Compost",
"Elite Chest",
}
}
local checkFuncs = {
local checkFuncs = {
--p.getItemSourceTables,
p.getItemSourceTables,
p.getCreationTable,
--p.getCreationTable,
--p.getItemSources,
--p.getItemSources,
--p.getItemLootSourceTable,
--p.getItemLootSourceTable,