Module:Items/SourceTables/Sandbox: Difference between revisions
From Melvor Idle
< Module:Items | SourceTables
(Separate Rune costs in Creation Table; Add Township and Superheat creation data into Creation Table;) |
(Centered Qty and Chance in Loot Tables; Make Creation Table sortable and give it a sticky header; Pull Arch and Mining & Alt Magic Gem sources into Creation Tables; Sorted tables by output quantity) |
||
Line 18: | Line 18: | ||
['melvorAoD:EarthGolem'] = 'Earth Golem (AoD)' | ['melvorAoD:EarthGolem'] = 'Earth Golem (AoD)' | ||
} | } | ||
local function doesRecipeHaveItemID(recipe, itemID) | local function doesRecipeHaveItemID(recipe, itemID) | ||
Line 49: | Line 32: | ||
function p._getCreationTableData(item, tableData) | function p._getCreationTableData(item, tableData) | ||
if tableData == nil then tableData = {} end | if tableData == nil then tableData = {} end | ||
local itemID = item.id | local itemID = item.id | ||
Line 68: | Line 38: | ||
local skillIDs = { | local skillIDs = { | ||
['Gathering'] = { | ['Gathering'] = { | ||
['Farming'] = { recipeKey = 'recipes | ['Farming'] = { recipeKey = 'recipes' }, | ||
['Woodcutting'] = { recipeKey = 'trees | ['Woodcutting'] = { recipeKey = 'trees' }, | ||
['Fishing'] = { recipeKey = 'fish | ['Fishing'] = { recipeKey = 'fish' }, | ||
['Firemaking'] = { recipeKey = 'logs | ['Firemaking'] = { recipeKey = 'logs' }, | ||
['Mining'] = { recipeKey = 'rockData | ['Mining'] = { recipeKey = 'rockData' }, | ||
['Cartography'] = { recipeKey = 'paperRecipes | ['Cartography'] = { recipeKey = 'paperRecipes' }, | ||
['Harvesting'] = { recipeKey = 'veinData | ['Harvesting'] = { recipeKey = 'veinData' } | ||
}, | }, | ||
['Artisan'] = { | ['Artisan'] = { | ||
Line 92: | Line 62: | ||
local skillData = SkillData[localSkillID] | local skillData = SkillData[localSkillID] | ||
local skill = skillData.name | local skill = skillData.name | ||
local reqs | 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 163: | Line 133: | ||
table.insert(tableData, { | table.insert(tableData, { | ||
['skill'] = skill, | ['skill'] = skill, | ||
['lvl'] = lvl, | |||
['reqs'] = reqs, | ['reqs'] = reqs, | ||
['isAbyssal'] = isAbyssal, | ['isAbyssal'] = isAbyssal, | ||
Line 186: | Line 157: | ||
local skillData = SkillData[localSkillID] | local skillData = SkillData[localSkillID] | ||
local skill = skillData.name | local skill = skillData.name | ||
local reqs | 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 213: | Line 184: | ||
if levelUnlock ~= nil then | if levelUnlock ~= nil then | ||
reqs = reqs .. '<br>' .. Icons._MasteryReq(item.name, levelUnlock.level, false) | reqs = reqs .. '<br>' .. Icons._MasteryReq(item.name, levelUnlock.level, false) | ||
end | end | ||
end | end | ||
Line 266: | Line 221: | ||
table.insert(tableData, { | table.insert(tableData, { | ||
['skill'] = skill, | ['skill'] = skill, | ||
[' | ['lvl'] = lvl, | ||
['reqs'] = reqs, | |||
['isAbyssal'] = isAbyssal, | ['isAbyssal'] = isAbyssal, | ||
['xp'] = xp, | ['xp'] = xp, | ||
Line 278: | Line 234: | ||
local reqPart, qtyPart = {}, {} | local reqPart, qtyPart = {}, {} | ||
for j, altCost in ipairs(recipe.alternativeCosts) do | for j, altCost in ipairs(recipe.alternativeCosts) do | ||
table.insert(tableData, { | table.insert(tableData, { | ||
['skill'] = skill, | ['skill'] = skill, | ||
[' | ['lvl'] = lvl, | ||
['reqs'] = reqs, | |||
['isAbyssal'] = isAbyssal, | ['isAbyssal'] = isAbyssal, | ||
['xp'] = xp, | ['xp'] = xp, | ||
['costs'] = | ['costs'] = Common.getCostString({ ["items"] = altCost.itemCosts, ["currencies"] = recipe.currencyCosts }), | ||
['qty'] = qty * altCost.quantityMultiplier, | ['qty'] = qty * altCost.quantityMultiplier, | ||
['source'] = | ['source'] = source, | ||
['time'] = time, | ['time'] = time, | ||
['maxTime'] = maxTime | ['maxTime'] = maxTime | ||
Line 303: | Line 249: | ||
-- 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, | ||
['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'] = | ['source'] = source, | ||
['time'] = time, | ['time'] = time, | ||
['maxTime'] = maxTime | ['maxTime'] = maxTime | ||
}) | }) | ||
end | end | ||
Line 327: | Line 290: | ||
local imgType = Magic._getSpellIconType(altSpell) | local imgType = Magic._getSpellIconType(altSpell) | ||
table.insert(tableData, { | table.insert(tableData, { | ||
['skill'] = 'Magic', | ['skill'] = 'Alt Magic', | ||
['reqs'] = Icons. | ['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, | ||
Line 348: | Line 312: | ||
table.insert(tableData, { | table.insert(tableData, { | ||
['skill'] = 'Astrology', | ['skill'] = 'Astrology', | ||
['lvl'] = 1, | |||
['reqs'] = Icons._SkillReq('Astrology', 1, false, (isAbyssal and 'melvorItA:Abyssal' or nil)), | ['reqs'] = Icons._SkillReq('Astrology', 1, false, (isAbyssal and 'melvorItA:Abyssal' or nil)), | ||
['isAbyssal'] = isAbyssal, | ['isAbyssal'] = isAbyssal, | ||
['qty'] = | ['qty'] = 1, | ||
['xp'] = (isAbyssal and 1238 or 5), -- Use the XP value for the first 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 nil) }), | ['source'] = Icons.Icon({ 'Astrology', type='skill', class=(isAbyssal and 'abyss-icon' or nil) }), | ||
Line 358: | Line 323: | ||
end | end | ||
-- Can we find this in an Archaeology digsite? | |||
for i, drop in ipairs(p._getItemArchSources(item)) do | |||
if drop.name ~= nil then | |||
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 | |||
-- Mining: Gems, and also Alt. Magic spells producing random gems | |||
local | if Shared.contains({'Gem', 'Superior Gem', 'Abyssal Gem'}, item.type) then | ||
local gemKeys = { 'randomGems', 'randomSuperiorGems', 'randomAbyssalGems' } | |||
for i, gemKey in ipairs(gemKeys) do | |||
local thisGem, totalGemWeight = nil, 0 | |||
for j, gem in ipairs(GameData.rawData[gemKey]) do | |||
totalGemWeight = totalGemWeight + gem.weight | |||
if gem.itemID == item.id then | |||
thisGem = gem | |||
end | |||
end | |||
if thisGem ~= nil then | |||
--local expIcon = '' | |||
local sourceTxt, lvl, isAbyssal = nil, nil, false | |||
if item.type == 'Abyssal Gem' then | |||
sourceTxt = '[[Mining#Abyssal Gems|Abyssal Gem]]' | |||
lvl = 1 | |||
isAbyssal = true | |||
elseif 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 | |||
lvl = 100 | |||
else | |||
sourceTxt = '[[Mining#Gems|Gem]]' | |||
-- Gems can only be found with any Mining level | |||
lvl = 1 | |||
end | |||
table.insert(tableData, { | |||
['skill'] = 'Mining', | |||
['lvl'] = lvl, | |||
['reqs'] = Icons._SkillReq('Mining', lvl, false, (isAbyssal and 'melvorItA:Abyssal' or nil)), | |||
['isAbyssal'] = isAbyssal, | |||
['minqty'] = thisGem.minQuantity, | |||
['qty'] = thisGem.maxQuantity, | |||
['source'] = sourceTxt, | |||
['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 | ||
if not showRequirements and tableData[1].reqs ~= tableData[i].reqs then | end | ||
showRequirements = true | |||
colspan = colspan + 1 | return tableData | ||
end | |||
function p.buildCreationTable(item, tableData) | |||
if Shared.tableIsEmpty(tableData) then return '' end | |||
table.sort(tableData, function(a, b) return (a.qty or 1) < (b.qty or 1) end) | |||
local showSource = false | |||
local showRequirements = false | |||
local showInputs = false | |||
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 | |||
for i, data in ipairs(tableData) do | |||
if not showSource and tableData[1].source ~= tableData[i].source then | |||
showSource = true | |||
colspan = colspan + 1 | |||
end | |||
if not showRequirements and tableData[1].reqs ~= tableData[i].reqs then | |||
showRequirements = true | |||
colspan = colspan + 1 | |||
end | end | ||
if not showInputs and tableData[1].costs ~= tableData[i].costs then | if not showInputs and tableData[1].costs ~= tableData[i].costs then | ||
Line 391: | Line 444: | ||
colspan = colspan + 1 | colspan = colspan + 1 | ||
end | end | ||
if not showOutputs and tableData[1].qty ~= tableData[i].qty then | if not showOutputs and (tableData[1].qty ~= tableData[i].qty or tableData[1].contents ~= tableData[i].contents) then | ||
showOutputs = true | showOutputs = true | ||
colspan = colspan + 1 | colspan = colspan + 1 | ||
Line 422: | Line 475: | ||
costsRow:wikitext(Icons.Icon({ matItem.name, type='item', qty=mat.quantity })) | costsRow:wikitext(Icons.Icon({ matItem.name, type='item', qty=mat.quantity })) | ||
end | end | ||
end | end | ||
else | else | ||
Line 434: | Line 483: | ||
local resultTable = mw.html.create('table') | local resultTable = mw.html.create('table') | ||
resultTable:addClass('wikitable') | resultTable:addClass('wikitable stickyHeader') | ||
local tableHeader = resultTable:tag('tr') | 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 | |||
if showSource then tableHeader:tag('th'):wikitext('Source') end | if showSource then tableHeader:tag('th'):wikitext('Source') end | ||
Line 446: | Line 500: | ||
if showChance then tableHeader:tag('th'):wikitext('Chance'):attr('colspan', 2) end | if showChance then tableHeader:tag('th'):wikitext('Chance'):attr('colspan', 2) end | ||
-- Populate table data with any unique entries (Ex: Ash's Inputs, Outputs, Exp, Time) | if makeSortable then | ||
-- Populate table data with any unique entries (Ex: Ash's Inputs, Outputs, Exp, Time) | |||
for i, data in ipairs(tableData) do | |||
local recipeRow = resultTable:tag('tr') | |||
if showSource then recipeRow:tag('td'):wikitext(data.source):attr('data-sort-value', data.skill) end | |||
if showRequirements then | |||
if data.reqs ~= nil then | |||
recipeRow:tag('td'):wikitext(data.reqs):attr('data-sort-value', (data.lvl or 0)) | |||
else | |||
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na'):attr('data-sort-value', 0) | |||
end | |||
end | end | ||
if showInputs then | |||
if data.costs ~= nil then | |||
addCostsRow(recipeRow, data, 1) | |||
else | |||
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na') | |||
end | |||
end | end | ||
if showRunes then | |||
if data.runeCost ~= nil then | |||
recipeRow:tag('td'):wikitext(data.runeCost):addClass('center') | |||
else | |||
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na') | |||
end | |||
end | end | ||
if showOutputs then | |||
local outputData = recipeRow:tag('td'):attr('data-sort-value', (data.qty or 1)) | |||
local outputData = recipeRow:tag('td'): | if data.contents ~= nil then | ||
if data.center then outputData:addClass('center') end | 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 | 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 | end | ||
if showTime then | |||
if data.time ~= nil then | |||
recipeRow:tag('td'):wikitext(Shared.timeString(data.time, true)):addClass('center'):attr('data-sort-value', data.time) | |||
else | |||
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na') | |||
end | |||
end | end | ||
if showChance then | |||
if data.weight ~= nil then | |||
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places | |||
local chance = data.weight / (data.totalWeight or 100) * 100 | |||
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 | ||
end | end | ||
Line 518: | Line 586: | ||
resultTable:tag('tr') | resultTable:tag('tr') | ||
:tag('th'):wikitext('Source'):css('text-align', 'right') | :tag('th'):wikitext('Source'):css('text-align', 'right') | ||
:tag('td'):attr('colspan', colspan):wikitext(tableData[1].source) | |||
end | end | ||
Line 534: | Line 602: | ||
end | end | ||
if showRunes and type(tableData[1].runeCost) == 'string' then | if not showRunes and type(tableData[1].runeCost) == 'string' then | ||
resultTable:tag('tr') | |||
:tag('th'):wikitext('Runes'):css('text-align', 'right') | |||
:tag('td'):wikitext(tableData[1].runeCost):addClass('center') | |||
end | end | ||
if not showOutputs and tableData[1].qty ~= nil then | if not showOutputs and (tableData[1].qty ~= nil or tableData[1].contents ~= nil) then | ||
local outputRow = resultTable:tag('tr') | local outputRow = resultTable:tag('tr') | ||
:tag('th'):wikitext('Outputs'):css('text-align', 'right') | :tag('th'):wikitext('Outputs'):css('text-align', 'right') | ||
if | if tableData[1].contents ~= nil then | ||
outputRow:tag('td'):wikitext(tableData[1]. | outputRow:tag('td'):wikitext(tableData[1].contents) | ||
else | else | ||
outputRow:tag('td'):attr('colspan', colspan):wikitext(Icons.Icon({ item.name, type='item', qty=(tableData[1].qty or 1) })) | 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 | |||
outputData:wikitext(Icons.Icon({ item.name, type='item', qty=(tableData[1].qty or 1) })) | |||
end | end | ||
end | end | ||
Line 554: | Line 627: | ||
resultTable:tag('tr') | resultTable:tag('tr') | ||
:tag('th'):wikitext('Base Exp'):css('text-align', 'right') | :tag('th'):wikitext('Base Exp'):css('text-align', 'right') | ||
:tag('td'):attr('colspan', colspan):wikitext(Num.formatnum(tableData[1].xp) .. xpText) | |||
end | end | ||
Line 562: | Line 635: | ||
local timeData = timeHeader:tag('td'):attr('colspan', colspan) | 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 | if tableData[1].maxTime ~= nil and tableData[1].maxTime > tableData[1].time then | ||
Line 570: | Line 643: | ||
if not showChance and tableData[1].weight ~= nil then | if not showChance and tableData[1].weight ~= nil then | ||
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places | |||
local chance = tableData[1].weight / (tableData[1].totalWeight or 100) * 100 | |||
local fmt = (chance < 0.10 and '%.2g') or '%.2f' | |||
local percent = string.format(fmt, chance) | |||
local chanceData = resultTable:tag('tr') | local chanceData = resultTable:tag('tr') | ||
:tag('th'):wikitext('Base Chance'):css('text-align', 'right') | :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 | ||
Line 1,115: | Line 1,191: | ||
function p._getItemLootSourceTable(item) | function p._getItemLootSourceTable(item) | ||
local resultPart = {} | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | table.insert(resultPart, '{| class="wikitable sortable stickyHeader col-3-center col-4-center"') | ||
table.insert(resultPart, '\r\n|- class="headerRow-0"') | table.insert(resultPart, '\r\n|- class="headerRow-0"') | ||
table.insert(resultPart, '\r\n!Source!!Level!! | table.insert(resultPart, '\r\n!Source!!Level!!Qty!!colspan="2"|Chance') | ||
--Set up function for adding rows | --Set up function for adding rows | ||
Line 1,135: | Line 1,211: | ||
end | end | ||
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon..' '..level) | table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon..' '..level) | ||
table.insert(rowPart, '\r\n| | table.insert(rowPart, '\r\n|data-sort-value="'..qty..'"|'..Num.formatnum(minqty)) | ||
if qty ~= minqty then table.insert(rowPart, ' - '..Num.formatnum(qty)) end | if qty ~= minqty then table.insert(rowPart, ' - '..Num.formatnum(qty)) end | ||
local chance = weight / totalWeight * 100 | local chance = weight / totalWeight * 100 | ||
Line 1,152: | Line 1,228: | ||
table.insert(rowPart, '\r\n|colspan="2" ') | table.insert(rowPart, '\r\n|colspan="2" ') | ||
else | else | ||
table.insert(rowPart, '\r\n| | table.insert(rowPart, '\r\n|data-sort-value="' .. chanceStr .. '"| ' .. Num.fraction(weight, totalWeight) .. '\r\n|') | ||
end | end | ||
end | end | ||
if weight == -1 then | if weight == -1 then | ||
--Weight of -1 means this is a weird row that has a variable percentage | --Weight of -1 means this is a weird row that has a variable percentage | ||
table.insert(rowPart, ' | table.insert(rowPart, 'data-sort-value="0"|Varies (see Thieving page)') | ||
else | else | ||
table.insert(rowPart, ' | table.insert(rowPart, 'data-sort-value="'.. chanceStr .. '"|'..chanceStr..'%') | ||
end | end | ||
return table.concat(rowPart) | return table.concat(rowPart) | ||
Line 1,180: | Line 1,256: | ||
level = Icons.Icon({'Combat', 'Monsters', notext=true}) .. ' Level ' .. Num.formatnum(monsterLevel), | level = Icons.Icon({'Combat', 'Monsters', notext=true}) .. ' Level ' .. Num.formatnum(monsterLevel), | ||
levelNum = monsterLevel, | levelNum = monsterLevel, | ||
minqty = drop.minQty, | minqty = drop.minQty, | ||
qty = drop.maxQty, | qty = drop.maxQty, | ||
Line 1,349: | Line 1,409: | ||
end | end | ||
--Make sure to return nothing if there are no drop sources | |||
if Shared.tableIsEmpty(dropRows) then return '' end | |||
table.sort(dropRows, function(a, b) | |||
--Make sure to return nothing if there are no drop sources | |||
if Shared.tableIsEmpty(dropRows) then return '' end | |||
table.sort(dropRows, function(a, b) | |||
if a.weight / a.totalWeight == b.weight / b.totalWeight then | if a.weight / a.totalWeight == b.weight / b.totalWeight then | ||
if a.minqty + a.qty == b.minqty + b.qty then | if a.minqty + a.qty == b.minqty + b.qty then | ||
Line 1,442: | Line 1,446: | ||
local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id) | local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id) | ||
if upgrade ~= nil then | if upgrade ~= nil then | ||
local | 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, { | table.insert(tableData, { | ||
['costs'] = | ['reqs'] = reqs, | ||
['qty'] = 1, | ['costs'] = Common.getCostString({ ["items"] = upgrade.itemCosts, ["currencies"] = upgrade.currencyCosts }), | ||
['source'] = '[[Upgrading Items|Item Upgrade]]' | ['qty'] = (upgrade.quantity or 1), | ||
['source'] = '[[Upgrading Items|Item ' .. (upgrade.isDowngrade and 'Downgrade' or 'Upgrade') ..']]' | |||
}) | }) | ||
end | end | ||
Line 1,505: | Line 1,513: | ||
table.insert(tableData, { | table.insert(tableData, { | ||
['skill'] = 'Magic', | ['skill'] = 'Alt Magic', | ||
['reqs'] = smithingReq .. '<br>' .. Icons. | ['lvl'] = spell.level, | ||
['reqs'] = smithingReq .. '<br>' .. Icons.Icon({'Alt Magic', type='skill', notext=true}) .. ' Level ' .. spell.level, | |||
['isAbyssal'] = false, | ['isAbyssal'] = false, | ||
['xp'] = spell.baseExperience, | ['xp'] = spell.baseExperience, | ||
Line 1,541: | Line 1,550: | ||
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) | |||
table.insert(tableData, { | table.insert(tableData, { | ||
['reqs'] = | ['lvl'] = lvl, | ||
['reqs'] = Common.getRequirementString(tradeDef.unlockRequirements), | |||
['isAbyssal'] = namespace == 'melvorItA', | ['isAbyssal'] = namespace == 'melvorItA', | ||
['costs'] = Icons.Icon({ resName, qty=resQty, type='resource' }), | ['costs'] = Icons.Icon({ resName, qty=resQty, type='resource' }), | ||
['qty'] = 1, | ['qty'] = 1, | ||
['source'] = Icons.Icon({'Township', 'Trader', type='skill'}), | ['source'] = Icons.Icon({ 'Township', 'Trader', type='skill' }), | ||
}) | }) | ||
break | break | ||
Line 1,557: | Line 1,569: | ||
end | end | ||
function p._getItemShopTableData(item, tableData) | function p._getItemShopTableData(item, tableData) | ||
if tableData == nil then tableData = {} end | if tableData == nil then tableData = {} end | ||
Line 1,567: | Line 1,578: | ||
local namespace, localID = Shared.getLocalID(purchase.id) | local namespace, localID = Shared.getLocalID(purchase.id) | ||
local source = nil | local source = nil | ||
-- Show icon text when it's the only | -- Show icon text when it's the only source of this item | ||
local notext = (Shared.tableCount(tableData) + Shared.tableCount(purchaseArray) > 1) | local notext = (Shared.tableCount(tableData) + Shared.tableCount(purchaseArray) > 1) | ||
Line 1,581: | Line 1,592: | ||
['reqs'] = Common.getRequirementString(purchase.purchaseRequirements), | ['reqs'] = Common.getRequirementString(purchase.purchaseRequirements), | ||
['isAbyssal'] = namespace == 'melvorItA', | ['isAbyssal'] = namespace == 'melvorItA', | ||
['costs'] = | ['costs'] = Common.getCostString(purchase.cost), | ||
['qty'] = Shop._getPurchaseContents(purchase, true, notext), | ['qty'] = purchaseData.qty, | ||
['contents'] = Shop._getPurchaseContents(purchase, true, notext), | |||
['source'] = source, | ['source'] = source, | ||
['center'] = notext | ['center'] = notext | ||
Line 1,611: | Line 1,623: | ||
table.insert(resultPart, sourceTable) | table.insert(resultPart, sourceTable) | ||
end | end | ||
local lootTable = p._getItemLootSourceTable(item) | local lootTable = p._getItemLootSourceTable(item) | ||
if lootTable ~= '' then | if lootTable ~= '' then | ||
Line 1,617: | Line 1,629: | ||
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,718: | Line 1,730: | ||
if found ~= nil then | if found ~= nil then | ||
table.insert(resultArray, { | table.insert(resultArray, { | ||
id = digSite.id, | id = digSite.id, | ||
Line 1,725: | Line 1,735: | ||
level = digSite.level, | level = digSite.level, | ||
size = sizeName, | size = sizeName, | ||
minQty = | minQty = found.minQuantity, | ||
maxQty = | maxQty = found.maxQuantity, | ||
dropWt = found.weight, | dropWt = found.weight, | ||
totalWt = sizeWeight}) | totalWt = sizeWeight | ||
}) | |||
end | end | ||
end | end | ||
Line 1,741: | Line 1,752: | ||
end | end | ||
--[[ | |||
-- Uncomment this block and execute 'p.test()' within the debug console | -- Uncomment this block and execute 'p.test()' within the debug console | ||
-- to test after making changes | -- to test after making changes | ||
Line 1,751: | Line 1,762: | ||
-- "Signet Ring Half (b)", | -- "Signet Ring Half (b)", | ||
-- "Astrology Lesser Relic", | -- "Astrology Lesser Relic", | ||
"Mysterious Stone", | |||
"Charge Stone of Rhaelyx", | |||
"Gold Topaz Ring", | |||
"Charcoal", | "Charcoal", | ||
"Ash", | "Ash", | ||
"Coal Ore", | "Coal Ore", | ||
"Golden Star", | |||
"Potion Box III", | |||
"Rune Essence", | "Rune Essence", | ||
"Dragonite Bar", | "Dragonite Bar", | ||
"Holy Dust", | |||
-- "Rune Platebody", | -- "Rune Platebody", | ||
"Arrow Shafts", | "Arrow Shafts", | ||
Line 1,790: | Line 1,805: | ||
"Mastery Token (Cooking)", | "Mastery Token (Cooking)", | ||
"Thief's Moneysack", | "Thief's Moneysack", | ||
-- "Slayer Deterer", | -- "Slayer Deterer", | ||
"Paper", | "Paper", | ||
-- "Lemon", | -- "Lemon", | ||
"Aranite Brush", | "Aranite Brush", | ||
"Charged Diamond Shard", | |||
"Barrier Dust", | "Barrier Dust", | ||
"Gloom Resin", | "Gloom Resin", | ||
Line 1,812: | Line 1,827: | ||
"Dragon Javelin", | "Dragon Javelin", | ||
"Skillers Body", | "Skillers Body", | ||
"Abyssal Compost", | |||
} | } | ||
local checkFuncs = { | local checkFuncs = { | ||
Line 1,839: | Line 1,855: | ||
end | end | ||
end | end | ||
--]] | |||
return p | return p |
Latest revision as of 06:14, 11 December 2024
Documentation for this module may be created at Module:Items/SourceTables/Sandbox/doc
local p = {}
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Common = require('Module:Common')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Magic = require('Module:Magic')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Shop = require('Module:Sandbox/Shop') -- TODO: Remove Sandbox/ when pushing live
local Monsters = require('Module:Monsters')
local Skills = require('Module:Skills')
local Num = require('Module:Number')
local SourceOverrides = {
['melvorAoD:EarthGolem'] = 'Earth Golem (AoD)'
}
local function doesRecipeHaveItemID(recipe, itemID)
if recipe.productId == itemID then
return true
elseif Shared.contains(recipe.primaryProducts, itemID) or Shared.contains(recipe.secondaryProducts, itemID) then
return true
elseif type(recipe.products) == 'table' then
return GameData.getEntityByProperty(recipe.products, 'itemID', itemID) ~= nil
end
return false
end
function p._getCreationTableData(item, tableData)
if tableData == nil then tableData = {} end
local itemID = item.id
--First figure out what skill is used to make this...
local skillIDs = {
['Gathering'] = {
['Farming'] = { recipeKey = 'recipes' },
['Woodcutting'] = { recipeKey = 'trees' },
['Fishing'] = { recipeKey = 'fish' },
['Firemaking'] = { recipeKey = 'logs' },
['Mining'] = { recipeKey = 'rockData' },
['Cartography'] = { recipeKey = 'paperRecipes' },
['Harvesting'] = { recipeKey = 'veinData' }
},
['Artisan'] = {
['Cooking'] = { },
['Smithing'] = { },
['Fletching'] = { },
['Crafting'] = { },
['Runecrafting'] = { },
['Herblore'] = { },
['Summoning'] = { }
}
}
-- Gathering skills
-- All follow a similar data structure
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
local skillData = SkillData[localSkillID]
local skill = skillData.name
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
local hasProduct = doesRecipeHaveItemID(recipe, itemID)
if hasProduct then
lvl, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe)
xp = recipe.baseAbyssalExperience or recipe.baseExperience
qty = recipe.baseQuantity or 1
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
if recipe.baseMinInterval ~= nil then
time = recipe.baseMinInterval / 1000
if recipe.baseMaxInterval ~= nil then
maxTime = recipe.baseMaxInterval / 1000
end
elseif recipe.baseInterval ~= nil then
time = recipe.baseInterval / 1000
elseif skillData.baseInterval ~= nil then
time = skillData.baseInterval / 1000
end
-- Custom Chance, Qty, and Costs data
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)
if itemChanceData ~= nil then
weight = itemChanceData.chance
elseif itemID == 'melvorD:Generous_Fire_Spirit' then
weight = 0.1
end
if Shared.contains({ 'melvorD:Generous_Fire_Spirit', 'melvorD:Coal_Ore', 'melvorTotH:Charcoal' }, itemID) then
costs = 'Any ' .. Icons.Icon({ 'Firemaking', 'Melvor Logs', img='Melvor Logo', section='Melvor Logs' })
else
local costItem = Items.getItemByID(recipe.logID)
costs = Icons.Icon({ costItem.name, type='item', qty=1 })
end
if itemID == 'melvorF:Ash' then
qty = time
elseif itemID == 'melvorItA:Withered_Ash' or itemID == 'melvorItA:Eternal_Ash' then
qty = math.max(math.floor(recipe.abyssalLevel / 10), 1)
end
elseif localSkillID == 'Cartography' then
time = 5
local costItem = Items.getItemByID(recipe.costs.items[1].id)
costs = Icons.Icon({ costItem.name, type='item', qty=1 })
elseif localSkillID == 'Harvesting' then
local itemChanceData = nil
totalWeight = 0
for i, product in ipairs(recipe.products) do
totalWeight = totalWeight + (product.weight or 0)
if product.itemID == itemID then itemChanceData = product end
end
if itemChanceData ~= nil then
weight = itemChanceData.weight
reqs = reqs .. '<br>' .. itemChanceData.minIntensityPercent .. '% ' .. Icons.Icon({ recipe.name, type='vein', notext=true }) .. ' Intensity'
end
end
-- Special requirements
if recipe.totalMasteryRequired ~= nil then
reqs = reqs .. '<br>' .. Icons.Icon({ 'Mastery', notext=true }) .. ' ' .. Num.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
end
table.insert(tableData, {
['skill'] = skill,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['costs'] = costs,
['qty'] = qty,
['source'] = source,
['time'] = time,
['maxTime'] = maxTime,
['weight'] = weight,
['totalWeight'] = totalWeight
})
-- Most recipes have a single item source or the item source data
-- is nearly all the same. The following items have some uniqueness
if not Shared.contains({ 'melvorF:Ash', 'melvorItA:Withered_Ash', 'melvorAoD:Paper' }, itemID) then break end
end
end
end
-- Artisan skills
-- Allow follow a similar data structure
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
local skillData = SkillData[localSkillID]
local skill = skillData.name
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
if recipe.productID == itemID or
(localSkillID == 'Cooking' and recipe.perfectCookID == itemID) or
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, itemID)) then
lvl, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe)
xp = recipe.baseAbyssalExperience or recipe.baseExperience
qty = recipe.baseQuantity or 1
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
if recipe.baseMinInterval ~= nil then
time = recipe.baseMinInterval / 1000
if recipe.baseMaxInterval ~= nil then
maxTime = recipe.baseMaxInterval / 1000
end
elseif recipe.baseInterval ~= nil then
time = recipe.baseInterval / 1000
elseif skillData.baseInterval ~= nil then
time = skillData.baseInterval / 1000
end
-- Special requirements
-- Potions have a mastery level requirement depending on the tier
if item.charges ~= nil and item.tier ~= nil then
local levelUnlock = GameData.getEntityByProperty(skillData.masteryLevelUnlocks, 'descriptionID', item.tier + 1)
if levelUnlock ~= nil then
reqs = reqs .. '<br>' .. Icons._MasteryReq(item.name, levelUnlock.level, false)
end
end
-- Materials & output quantity
-- Special case for Summoning recipes
if localSkillID == 'Summoning' then
local shardCostArray, otherCostArray = {}, {}
local recipeCost = 0
if isAbyssal == true then
recipeCost = skillData.recipeAPCost
else
recipeCost = skillData.recipeGPCost
end
-- Shards
for j, itemCost in ipairs(recipe.itemCosts) do
local shard = Items.getItemByID(itemCost.id)
if shard ~= nil then
table.insert(shardCostArray, Icons.Icon({ shard.name, type='item', qty=itemCost.quantity }))
end
end
-- Other costs
table.insert(otherCostArray, Common.getCostString({ ["items"] = {}, ["currencies"] = recipe.currencyCosts }))
for j, nonShardID in ipairs(recipe.nonShardItemCosts) do
local nonShard = Items.getItemByID(nonShardID)
if nonShard ~= nil then
local itemValue = math.max(nonShard.sellsFor, 20)
local nonShardQty = math.max(1, math.ceil(recipeCost / itemValue))
table.insert(otherCostArray, Icons.Icon({ nonShard.name, type='item', qty=nonShardQty }))
end
end
costs = table.concat(shardCostArray, '<br>')
if not Shared.tableIsEmpty(otherCostArray) then
local costLen = Shared.tableCount(otherCostArray)
costs = costs .. '<br>' .. (costLen == 1 and '' or 'and one of the following:<br>') .. table.concat(otherCostArray, "<br>'''OR''' ")
end
reqs = reqs .. '<br>At least 1 ' .. Icons.Icon({ 'Summoning', item.name, img=item.name, type='mark', section='Summoning Marks' }) .. ' mark discovered'
table.insert(tableData, {
['skill'] = skill,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['costs'] = costs,
['qty'] = qty,
['source'] = source,
['time'] = time
})
-- Some items (such as Arrow shafts) have multiple recipes
elseif type(recipe.alternativeCosts) == 'table' then
local reqPart, qtyPart = {}, {}
for j, altCost in ipairs(recipe.alternativeCosts) do
table.insert(tableData, {
['skill'] = skill,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['costs'] = Common.getCostString({ ["items"] = altCost.itemCosts, ["currencies"] = recipe.currencyCosts }),
['qty'] = qty * altCost.quantityMultiplier,
['source'] = source,
['time'] = time,
['maxTime'] = maxTime
})
end
-- Finally, normal recipes with a single set of item costs
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, {
['skill'] = skill,
['lvl'] = lvl,
['reqs'] = reqs,
['isAbyssal'] = isAbyssal,
['xp'] = xp,
['costs'] = Common.getCostString({ ["items"] = recipe.itemCosts, ["currencies"] = recipe.currencyCosts }),
['qty'] = qty,
['source'] = source,
['time'] = time,
['maxTime'] = maxTime
})
end
end
end
end
-- Alt. Magic, excludes spells which can produce a variety of items, such as Gems and Bars
-- Bars are handled by getItemSuperheatTable()
-- Gems are handled by _getItemLootSourceTable()
for i, altSpell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
if altSpell.produces == itemID then
local imgType = Magic._getSpellIconType(altSpell)
table.insert(tableData, {
['skill'] = 'Alt Magic',
['lvl'] = altSpell.level,
['reqs'] = Icons.Icon({'Alt Magic', type='skill', notext=true}) .. ' Level ' .. altSpell.level,
['isAbyssal'] = false,
['xp'] = altSpell.baseExperience,
['costs'] = Magic._getAltSpellCostText(altSpell),
['qty'] = altSpell.productionRatio,
['source'] = Icons.Icon({ altSpell.name, type=imgType }),
['time'] = 2,
['runeCost'] = Magic._getSpellRunes(altSpell)
})
end
end
-- Add in Astrology creation items manually since each constellation has (mostly)
-- the same creation information so looping through them is not necessary
local stardustChanceData = GameData.getEntityByProperty(SkillData.Astrology.baseRandomItemChances, 'itemID', itemID)
if stardustChanceData ~= nil then
local namespace, localID = Shared.getLocalID(stardustChanceData.itemID)
local isAbyssal = namespace == 'melvorItA'
table.insert(tableData, {
['skill'] = 'Astrology',
['lvl'] = 1,
['reqs'] = Icons._SkillReq('Astrology', 1, false, (isAbyssal and 'melvorItA:Abyssal' or nil)),
['isAbyssal'] = isAbyssal,
['qty'] = 1,
['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 nil) }),
['time'] = 3,
['weight'] = stardustChanceData.chance
})
end
-- Can we find this in an Archaeology digsite?
for i, drop in ipairs(p._getItemArchSources(item)) do
if drop.name ~= nil then
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
-- Mining: Gems, and also Alt. Magic spells producing random gems
if Shared.contains({'Gem', 'Superior Gem', 'Abyssal Gem'}, item.type) then
local gemKeys = { 'randomGems', 'randomSuperiorGems', 'randomAbyssalGems' }
for i, gemKey in ipairs(gemKeys) do
local thisGem, totalGemWeight = nil, 0
for j, gem in ipairs(GameData.rawData[gemKey]) do
totalGemWeight = totalGemWeight + gem.weight
if gem.itemID == item.id then
thisGem = gem
end
end
if thisGem ~= nil then
--local expIcon = ''
local sourceTxt, lvl, isAbyssal = nil, nil, false
if item.type == 'Abyssal Gem' then
sourceTxt = '[[Mining#Abyssal Gems|Abyssal Gem]]'
lvl = 1
isAbyssal = true
elseif 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
lvl = 100
else
sourceTxt = '[[Mining#Gems|Gem]]'
-- Gems can only be found with any Mining level
lvl = 1
end
table.insert(tableData, {
['skill'] = 'Mining',
['lvl'] = lvl,
['reqs'] = Icons._SkillReq('Mining', lvl, false, (isAbyssal and 'melvorItA:Abyssal' or nil)),
['isAbyssal'] = isAbyssal,
['minqty'] = thisGem.minQuantity,
['qty'] = thisGem.maxQuantity,
['source'] = sourceTxt,
['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
return tableData
end
function p.buildCreationTable(item, tableData)
if Shared.tableIsEmpty(tableData) then return '' end
table.sort(tableData, function(a, b) return (a.qty or 1) < (b.qty or 1) end)
local showSource = false
local showRequirements = false
local showInputs = false
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
for i, data in ipairs(tableData) do
if not showSource and tableData[1].source ~= tableData[i].source then
showSource = true
colspan = colspan + 1
end
if not showRequirements and tableData[1].reqs ~= tableData[i].reqs then
showRequirements = true
colspan = colspan + 1
end
if not showInputs and tableData[1].costs ~= tableData[i].costs then
showInputs = true
colspan = colspan + 1
end
if not showRunes and tableData[1].runeCost ~= tableData[i].runeCost then
showRunes = true
colspan = colspan + 1
end
if not showOutputs and (tableData[1].qty ~= tableData[i].qty or tableData[1].contents ~= tableData[i].contents) then
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
colspan = math.max(colspan, 1)
local function addCostsRow(row, data, span)
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
else
local costStr = data.costs:gsub(', ', '<br>')
costsRow:wikitext(costStr)
end
end
local resultTable = mw.html.create('table')
resultTable:addClass('wikitable stickyHeader')
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
if showSource then tableHeader:tag('th'):wikitext('Source') end
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 makeSortable then
-- Populate table data with any unique entries (Ex: Ash's Inputs, Outputs, Exp, Time)
for i, data in ipairs(tableData) do
local recipeRow = resultTable:tag('tr')
if showSource then recipeRow:tag('td'):wikitext(data.source):attr('data-sort-value', data.skill) end
if showRequirements then
if data.reqs ~= nil then
recipeRow:tag('td'):wikitext(data.reqs):attr('data-sort-value', (data.lvl or 0))
else
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na'):attr('data-sort-value', 0)
end
end
if showInputs then
if data.costs ~= nil then
addCostsRow(recipeRow, data, 1)
else
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na')
end
end
if showRunes then
if data.runeCost ~= nil then
recipeRow:tag('td'):wikitext(data.runeCost):addClass('center')
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
if showTime then
if data.time ~= nil then
recipeRow:tag('td'):wikitext(Shared.timeString(data.time, true)):addClass('center'):attr('data-sort-value', data.time)
else
recipeRow:tag('td'):wikitext('N/A'):addClass('table-na')
end
end
if showChance then
if data.weight ~= nil then
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
local chance = data.weight / (data.totalWeight or 100) * 100
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
end
-- Add all non-unique data below the table data (Ex: Ash's Source, Requires, Chance)
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
if not showRequirements and tableData[1].reqs ~= nil then
local reqRow = resultTable:tag('tr')
:tag('th'):wikitext('Requires'):css('text-align', 'right')
:tag('td'):wikitext(tableData[1].reqs):attr('colspan', colspan)
end
if not showInputs and tableData[1].costs ~= nil then
local costRow = resultTable:tag('tr')
:tag('th'):wikitext('Costs'):css('text-align', 'right')
addCostsRow(costRow, tableData[1], colspan)
end
if not showRunes and type(tableData[1].runeCost) == 'string' then
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
outputData:wikitext(Icons.Icon({ item.name, type='item', qty=(tableData[1].qty or 1) }))
end
end
if not showXP and tableData[1].xp ~= nil then
local xpText = (tableData[1].isAbyssal and ' AXP' or ' XP')
resultTable:tag('tr')
:tag('th'):wikitext('Base Exp'):css('text-align', 'right')
:tag('td'):attr('colspan', colspan):wikitext(Num.formatnum(tableData[1].xp) .. xpText)
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)
: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
if not showChance and tableData[1].weight ~= nil then
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
local chance = tableData[1].weight / (tableData[1].totalWeight or 100) * 100
local fmt = (chance < 0.10 and '%.2g') or '%.2f'
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
return tostring(resultTable)
end
function p.getCreationTable(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._getCreationTableData(item), item)
end
function p._getItemSources(item, asList, addCategories, separator)
local lineArray = {}
local categoryArray = {}
local sep = separator or ','
--Alright, time to go through all the ways you can get an item...
--First up: Can we kill somebody and take theirs?
local killStrPart = {}
for i, monster in ipairs(GameData.rawData.monsters) do
local isDrop = false
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
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
-- Is the item dropped from any dungeon?
local dungeonStrPart = {}
local dungeonEntities = {
['Dungeon'] = GameData.rawData.dungeons,
['The Abyss'] = GameData.rawData.abyssDepths
}
for entity, dungeons in pairs(dungeonEntities) do
for i, dungeon in ipairs(dungeons) do
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='combatArea', notext=true}))
elseif dungeon.eventID ~= nil then
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
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
for i, stronghold in ipairs(GameData.rawData.strongholds) do
for tier, tierData in pairs(stronghold.tiers) do
if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
for i, reward in ipairs(tierData.rewards.items) do
if reward.id == item.id then
table.insert(dungeonStrPart, Icons.Icon({stronghold.name, type='combatArea', notext=true}))
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
-- Can we find it in an openable item?
local lootPart = {}
for i, item2 in ipairs(GameData.rawData.items) do
if item2.dropTable ~= nil then
for j, loot in ipairs(item2.dropTable) do
if loot.itemID == item.id then
table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true}))
break
end
end
end
end
if not Shared.tableIsEmpty(lootPart) then
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, sep))
end
-- Is the item a result of upgrading/downgrading another item?
local upgradePart = { up = {}, down = {} }
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
if item.id == upgrade.upgradedItemID then
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
for catName, parts in pairs(upgradePart) do
if not Shared.tableIsEmpty(parts) then
if not upgradeCat then
table.insert(categoryArray, '[[Category:Upgraded Items]]')
upgradeCat = true
end
local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading'
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, sep))
end
end
--Next: Can we take it from somebody else -without- killing them?
local thiefItems = Skills.getThievingSourcesForItem(item.id)
if type(thiefItems) == 'table' then
local includedNPCs = {}
local thiefPart = {}
for i, thiefRow in ipairs(thiefItems) do
if thiefRow.npc == 'all' then
--if 'all' is the npc, this is a rare item so just say 'Thieving level 1'
table.insert(lineArray, Icons._SkillReq('Thieving', 1))
elseif not Shared.contains(includedNPCs, thiefRow.npc) then
table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
table.insert(includedNPCs, thiefRow.npc)
end
end
if not Shared.tableIsEmpty(thiefPart) then
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, sep))
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
--Check if we can make it ourselves
local skillIDs = {
['Gathering'] = {
['Woodcutting'] = { recipeKey = 'trees' },
['Fishing'] = { recipeKey = 'fish' },
['Firemaking'] = { recipeKey = 'logs' },
['Mining'] = { recipeKey = 'rockData' },
['Farming'] = { recipeKey = 'recipes' },
['Harvesting'] = { recipeKey = 'veinData' }
},
['Artisan'] = {
['Cooking'] = { },
['Smithing'] = { },
['Fletching'] = { },
['Crafting'] = { },
['Runecrafting'] = { },
['Herblore'] = { },
['Summoning'] = { }
}
}
-- Gathering skills
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
local skillData = SkillData[localSkillID]
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
-- Artisan skills
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
local skillData = SkillData[localSkillID]
local skill = skillData.name
for i, recipe in ipairs(skillData.recipes) do
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
-- Township trading
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
local found = false
for j, tradeDef in ipairs(tsResource.items) do
if tradeDef.itemID == item.id then
found = true
local levelReq = nil
if tradeDef.unlockRequirements ~= nil then
for k, req in ipairs(tradeDef.unlockRequirements) do
if req.type == 'SkillLevel' and req.skillID == 'melvorD:Township' then
levelReq = req.level
break
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
if found then
break
end
end
-- Archaeology sources
-- Digsites
for i, digsite in ipairs(SkillData.Archaeology.digSites) do
local found = false
for artefactType, artefactItems in pairs(digsite.artefacts) do
for j, itemDef in ipairs(artefactItems) do
if itemDef.itemID == item.id then
table.insert(lineArray, Icons._SkillReq(SkillData.Archaeology.name, digsite.level))
found = true
break
end
end
if found then
break
end
end
if found then
break
end
end
-- Museum rewards
for i, museumReward in ipairs(SkillData.Archaeology.museumRewards) do
if type(museumReward.items) == 'table' and Shared.contains(museumReward.items, item.id) then
table.insert(lineArray, Icons.Icon('Museum'))
break
end
end
-- Cartography
-- Paper
for i, recipe in ipairs(SkillData.Cartography.paperRecipes) do
if recipe.productId == item.id then
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
break
end
end
-- POI discovery rewards
for i, worldMap in ipairs(SkillData.Cartography.worldMaps) do
local found = false
for j, poi in ipairs(worldMap.pointsOfInterest) do
if type(poi.discoveryRewards) == 'table' and type(poi.discoveryRewards.items) == 'table' then
for k, itemDef in ipairs(poi.discoveryRewards.items) do
if itemDef.id == item.id then
-- Find level for POI hex
local level = 1
local poiHex = nil
local skillID = SkillData.Cartography.skillID
for m, hex in ipairs(worldMap.hexes) do
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
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
-- Travel events
for i, event in ipairs(SkillData.Cartography.travelEvents) do
local found = false
if type(event.rewards) == 'table' and type(event.rewards.items) == 'table' then
for j, itemDef in ipairs(event.rewards.items) do
if itemDef.id == item.id and itemDef.quantity > 0 then
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
found = true
break
end
end
if found then
break
end
end
end
--AstrologyCheck
for i, dustDrop in ipairs(SkillData.Astrology.baseRandomItemChances) do
if dustDrop.itemID == item.id then
table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'}))
end
end
-- Woodcutting
-- Raven Nest
if item.id == SkillData.Woodcutting.ravenNestItemID then
local levelReq = nil
for i, tree in ipairs(SkillData.Woodcutting.trees) do
if tree.canDropRavenNest and (levelReq == nil or tree.level < levelReq) then
levelReq = tree.level
end
end
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, levelReq))
-- Bird Nest, Ash, and Mushroom
elseif Shared.contains({
SkillData.Woodcutting.nestItemID,
SkillData.Woodcutting.ashItemID,
SkillData.Woodcutting.mushroomItemID
}, item.id) then
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, 1))
end
-- Fishing
-- Junk
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
elseif item.id == SkillData.Fishing.lostChestItem then
table.insert(lineArray, Icons._SkillReq(SkillData.Fishing.name, 100))
end
-- Specials
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do
if GameData.getEntityByProperty(specialItem.drops, 'itemID', item.id) ~= nil then
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]')
end
end
-- Firemaking: Coal
if Shared.contains({SkillData.Firemaking.coalItemID,
SkillData.Firemaking.ashItemID,
SkillData.Firemaking.charcoalItemID,
SkillData.Firemaking.fireSpiritItemID,
SkillData.Firemaking.diamondItemID
}, item.id) then
table.insert(lineArray, Icons._SkillReq(SkillData.Firemaking.name, 1))
end
-- Mining: Gems
if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or
GameData.getEntityByProperty('randomSuperiorGems', 'itemID', item.id) ~= nil or
GameData.getEntityByProperty('randomAbyssalGems', 'itemID', item.id) ~= nil) then
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
-- General rare drops for non-combat skills
-- Includes items like Circlet/Jewel of Rhaelyx, Mysterious stones, Signet ring half (a),
-- relics (for Ancient Relics mode)
local skillIconList, subText = {}, ''
for i, skillDataAll in ipairs(GameData.rawData.skillData) do
local skillData = skillDataAll.data
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
if type(skillData.rareDrops) == 'table' then
for j, rareDrop in ipairs(skillData.rareDrops) do
local isAltItem = (rareDrop.altItemID ~= nil and rareDrop.altItemID == item.id)
if isAltItem or rareDrop.itemID == item.id then
if Shared.tableIsEmpty(skillIconList) then
-- Initialize subText
if isAltItem then
local wornItem = Items.getItemByID(rareDrop.itemID)
subText = ' while wearing ' .. Icons.Icon({wornItem.name, type='item'})
elseif rareDrop.altItemID ~= nil then
-- There exists an alt item, but we are not searching for it
local altItem = Items.getItemByID(rareDrop.altItemID)
subText = ' if not worn (Instead of ' .. Icons.Icon({altItem.name, type='item'}) .. ')'
elseif rareDrop.itemID == 'melvorD:Mysterious_Stone' then
local foundItem = Items.getItemByID('melvorD:Crown_of_Rhaelyx')
subText = '<br>after finding ' .. Icons.Icon({foundItem.name, type='item'})
end
if type(rareDrop.gamemodes) == 'table' then
local gamemodeText = {}
for k, gamemodeID in ipairs(rareDrop.gamemodes) do
local gamemode = GameData.getEntityByID('gamemodes', gamemodeID)
if gamemode ~= nil then
table.insert(gamemodeText, gamemode.name)
end
end
if not Shared.tableIsEmpty(gamemodeText) then
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
if not Shared.tableIsEmpty(skillIconList) then
table.insert(lineArray, 'Any action in: ' .. table.concat(skillIconList, ', ') .. subText)
skillIconList, subText = {}, ''
end
-- Supplementary stuff on top of general rare drops
if item.id == 'melvorD:Gold_Topaz_Ring' then
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
elseif item.id == 'melvorD:Signet_Ring_Half_B' then
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
--Tokens are from the appropriate skill
if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then
for localSkillID, skillData in pairs(SkillData) do
if skillData.masteryTokenID ~= nil and skillData.masteryTokenID == item.id then
table.insert(lineArray, Icons._SkillReq(skillData.name, 1))
break
end
end
end
-- Golbin Raid exclusive items
if item.golbinRaidExclusive then
table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'}))
end
--Shop items (including special items like gloves that aren't otherwise listed)
if not Shared.tableIsEmpty(Shop.getItemSourceArray(item.id)) then
table.insert(lineArray, Icons.Icon({'Shop'}))
end
--Easter Eggs (manual list 'cause don't have a better way to do that)
if Shared.contains(Items.EasterEggs, item.name) then
table.insert(lineArray, '[[Easter Eggs]]')
end
-- Event exclusive items (also a manual list)
if Shared.contains(Items.EventItems, item.name) then
table.insert(lineArray, '[[Events]]')
end
-- Township Task reward
for _, task in ipairs(SkillData.Township.tasks) do
if task.rewards.items[1] ~= nil then -- Skip tasks with no items
if GameData.getEntityByID(task.rewards.items, item.id) then
table.insert(lineArray, Icons.Icon({'Tasks', type='township'}))
break
end
end
end
local resultPart = {}
if asList then
table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
else
table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br>") .. '</div>')
end
if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
return table.concat(resultPart)
end
function p.getItemSources(frame)
local itemName = frame.args ~= nil and frame.args[1] or frame
local item = Items.getItem(itemName)
local asList = false
local addCategories = false
if frame.args ~= nil then
asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
end
if item == nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end
return p._getItemSources(item, asList, addCategories)
end
function p._getItemLootSourceTable(item)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader col-3-center col-4-center"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!Source!!Level!!Qty!!colspan="2"|Chance')
--Set up function for adding rows
local buildRow = function(source, level, levelNum, minqty, qty, weight, totalWeight, expIcon)
if minqty == nil then minqty = 1 end
if expIcon == nil then expIcon = '' end
if level == nil then level = 'N/A' end
local rowPart = {}
table.insert(rowPart, '\r\n|-')
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
-- Retrieve numeric level value for sorting, or remove anything between [[]]
local levelValue = ''
if levelNum ~= nil then
levelValue = tostring(levelNum)
else
levelValue = level:match('%[%[.-%]%]%s*(%w+)$') or ''
end
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon..' '..level)
table.insert(rowPart, '\r\n|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|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, 'data-sort-value="0"|Varies (see Thieving page)')
else
table.insert(rowPart, '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
--First up: Can we kill somebody and take theirs?
for i, drop in ipairs(p._getItemMonsterSources(item)) do
local monster = GameData.getEntityByID('monsters', drop.id)
local iconName = monster.name
if SourceOverrides[drop.id] ~= nil then
iconName = SourceOverrides[drop.id]
end
if monster ~= nil then
local monsterLevel = Monsters._getMonsterCombatLevel(monster)
table.insert(dropRows, {
source = Icons.Icon({iconName, type='monster'}),
level = Icons.Icon({'Combat', 'Monsters', notext=true}) .. ' Level ' .. Num.formatnum(monsterLevel),
levelNum = monsterLevel,
minqty = drop.minQty,
qty = drop.maxQty,
weight = drop.dropWt,
totalWeight = drop.totalWt,
expIcon = Icons.getExpansionIcon(drop.id)})
end
end
-- Is the item dropped from any dungeon?
local dungeonEntities = {
['Dungeon'] = GameData.rawData.dungeons,
['The Abyss'] = GameData.rawData.abyssDepths
}
for entity, dungeons in pairs(dungeonEntities) do
for i, dungeon in ipairs(dungeons) do
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
table.insert(dropRows, {
source = Icons.Icon({dungeon.name, type='combatArea'}),
level = '[['..entity..']]',
minqty = 1,
qty = 1,
weight = 1,
totalWeight = 1,
expIcon = Icons.getExpansionIcon(dungeon.id)})
elseif dungeon.eventID ~= nil then
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
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
for i, stronghold in ipairs(GameData.rawData.strongholds) do
for tier, tierData in pairs(stronghold.tiers) do
if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
for i, reward in ipairs(tierData.rewards.items) do
if reward.id == item.id then
table.insert(dropRows, {
source = Icons.Icon({stronghold.name, type='combatArea'}),
level = '[[Strongholds|'..tier..']]',
minqty = 1,
qty = 1,
weight = tierData.rewards.chance,
totalWeight = 100,
expIcon = Icons.getExpansionIcon(stronghold.id)})
end
end
end
end
end
-- Can we find it in an openable item?
for i, item2 in ipairs(GameData.rawData.items) do
if item2.dropTable ~= nil then
local minQty, maxQty, wt, totalWt = 1, 1, 0, 0
for j, loot in ipairs(item2.dropTable) do
totalWt = totalWt + loot.weight
if loot.itemID == item.id then
wt = loot.weight
minQty = loot.minQuantity
maxQty = loot.maxQuantity
end
end
if wt > 0 then
local sourceTxt = Icons.Icon({item2.name, type='item'})
table.insert(dropRows, {
source = sourceTxt,
level = '[[Chest]]',
minqty = minQty,
qty = maxQty,
weight = wt,
totalWeight = totalWt,
expIcon = Icons.getExpansionIcon(item2.id)})
end
end
end
-- Can it be obtained from Thieving?
local thiefItems = Skills.getThievingSourcesForItem(item.id)
for i, thiefRow in ipairs(thiefItems) do
local sourceTxt = ''
if thiefRow.npc == 'all' then
sourceTxt = 'Thieving Rare Drop'
else
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
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
-- Fishing: Junk & Specials
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
local fishSource = '[[Fishing#Junk|Junk]]'
local fishType = Icons.Icon({'Fishing', type='skill'})
local fishTotWeight = Shared.tableCount(SkillData.Fishing.junkItemIDs)
table.insert(dropRows, {
source = fishSource,
level = Icons._SkillReq("Fishing", 1),
levelNum = 1,
minqty = 1,
qty = 1,
weight = 1,
totalWeight = fishTotWeight})
else
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
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
--Make sure to return nothing if there are no drop sources
if Shared.tableIsEmpty(dropRows) then return '' end
table.sort(dropRows, function(a, b)
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)
end
function p.getItemLootSourceTable(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._getItemLootSourceTable(item)
end
function p._getItemUpgradeTableData(item, tableData)
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
return smithRecipe
end
end
function p._getItemSuperheatTableData(item, tableData)
if tableData == nil then tableData = {} end
-- Validate that the item can be superheated
local smithRecipe = p._getSuperheatSmithRecipe(item)
if smithRecipe == nil then
return Shared.printError('The item "' .. item.name .. '" cannot be superheated')
end
local oreStringPart, coalString, smithingReq = {}, '', Icons._SkillReq('Smithing', smithRecipe.level, false)
for i, mat in ipairs(smithRecipe.itemCosts) do
local matItem = Items.getItemByID(mat.id)
if mat.id == 'melvorD:Coal_Ore' then
coalString = Icons.Icon({ matItem.name, type='item', qty=mat.quantity })
else
table.insert(oreStringPart, Icons.Icon({ matItem.name, type='item', qty=mat.quantity }))
end
end
--Loop through all the variants
local spells = Magic.getSpellsProducingItem(item.id)
for i, spell in ipairs(spells) do
if spell.specialCost ~= nil and Shared.contains({ 'BarIngredientsWithCoal', 'BarIngredientsWithoutCoal' }, spell.specialCost.type) then
local imgType = Magic._getSpellIconType(spell)
local costs = table.concat(oreStringPart, '<br>')
if spell.specialCost.type == 'BarIngredientsWithCoal' and coalString ~= '' then
costs = costs .. '<br>' .. coalString
end
table.insert(tableData, {
['skill'] = 'Alt Magic',
['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
function p.getItemSuperheatTable(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._getItemSuperheatTableData(item), item)
end
function p._getTownshipTraderTableData(item, tableData)
if tableData == nil then tableData = {} end
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
for j, tradeDef in ipairs(tsResource.items) do
if tradeDef.itemID == item.id then
-- Item found, insert the data
local res = GameData.getEntityByID(SkillData.Township.resources, tsResource.resourceID)
local resName = (res ~= nil and res.name) or 'Unknown'
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)
table.insert(tableData, {
['lvl'] = lvl,
['reqs'] = Common.getRequirementString(tradeDef.unlockRequirements),
['isAbyssal'] = namespace == 'melvorItA',
['costs'] = Icons.Icon({ resName, qty=resQty, type='resource' }),
['qty'] = 1,
['source'] = Icons.Icon({ 'Township', 'Trader', type='skill' }),
})
break
end
end
end
return tableData
end
function p._getItemShopTableData(item, tableData)
if tableData == nil then tableData = {} end
local purchaseArray = Shop.getItemSourceArray(item.id)
for i, purchaseData in ipairs(purchaseArray) do
local purchase = purchaseData.purchase
local namespace, localID = Shared.getLocalID(purchase.id)
local source = nil
-- Show icon text when it's the only source of this item
local notext = (Shared.tableCount(tableData) + Shared.tableCount(purchaseArray) > 1)
if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
source = Shop._getPurchaseExpansionIcon(purchase) .. Common.getPurchaseIcon({purchase})
-- Always show icon text when there's multiple items
-- notext = false
else
source = Icons.Icon({'Shop'}) .. ' Purchase'
end
table.insert(tableData, {
['reqs'] = Common.getRequirementString(purchase.purchaseRequirements),
['isAbyssal'] = namespace == 'melvorItA',
['costs'] = Common.getCostString(purchase.cost),
['qty'] = purchaseData.qty,
['contents'] = Shop._getPurchaseContents(purchase, true, notext),
['source'] = source,
['center'] = notext
})
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
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, sourceTable)
end
local lootTable = p._getItemLootSourceTable(item)
if lootTable ~= '' then
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
table.insert(resultPart, '===Loot===\r\n'..lootTable)
end
return table.concat(resultPart)
end
function p.getItemSourceTables(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._getItemSourceTables(item)
end
function p.getCombatPassiveSlotItems(frame)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\r\n')
table.insert(resultPart, '|-\r\n')
table.insert(resultPart, '!colspan="2"|Item\r\n! Passive\r\n')
local itemArray = Items.getItems(function(item) return item.validSlots ~= nil and (item.golbinRaidExclusive == nil or not item.golbinRaidExclusive) and Shared.contains(item.validSlots, 'Passive') end)
for i, item in ipairs(itemArray) do
local passiveDesc = item.customDescription or Modifiers.getModifiersText(item.modifiers, false, false, 10)
table.insert(resultPart, '|-\r\n')
table.insert(resultPart, '! '..Icons.Icon({item.name, type='item', notext='true'})..'\r\n! '..Icons.Icon({item.name, type='item', noicon=true})..'\r\n')
table.insert(resultPart, '| '..passiveDesc..'\r\n')
end
table.insert(resultPart, '|}')
return table.concat(resultPart)
end
function p._getItemMonsterSources(item)
local resultArray = {}
for i, monster in ipairs(GameData.rawData.monsters) do
local chance = 0
local weight = 0
local minQty = 1
local maxQty = 1
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
maxQty = (monster.bones.quantity ~= nil and monster.bones.quantity) or 1
minQty = maxQty
chance = 1
weight = 1
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
maxQty = math.max(math.floor(Monsters._getMonsterStat(monster, 'Barrier') / 10 / 20), 1)
minQty = maxQty
chance = 1
elseif monster.lootTable ~= nil and not Monsters._isDungeonOnlyMonster(monster) 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
weight = weight + loot.weight
if loot.itemID == item.id then
chance = loot.weight
minQty = loot.minQuantity
maxQty = loot.maxQuantity
end
end
local lootChance = monster.lootChance ~= nil and (monster.bones == nil or monster.bones.itemID ~= item.id) and monster.lootChance or 100
chance = chance * lootChance
weight = weight * 100
chance, weight = Num.fractionpair(chance, weight)
end
if chance > 0 then
-- Item drops when the monster is killed
table.insert(resultArray, {id = monster.id, dropWt = chance, totalWt = weight, minQty = minQty, maxQty = maxQty})
end
end
return resultArray
end
function p.getItemMonsterSources(itemName)
local item = Items.getItem(itemName)
return p._getItemMonsterSources(item)
end
function p._getItemArchSources(item)
local check = false
local itemID = item.id
local resultArray = {}
for i, digSite in pairs(SkillData.Archaeology.digSites) do
for sizeName, size in pairs(digSite.artefacts) do
local found = nil
local sizeWeight = 0
for k, artefact in pairs(size) do
sizeWeight = sizeWeight + artefact.weight
if artefact.itemID == itemID then
found = artefact
end
end
if found ~= nil then
table.insert(resultArray, {
id = digSite.id,
name = digSite.name,
level = digSite.level,
size = sizeName,
minQty = found.minQuantity,
maxQty = found.maxQuantity,
dropWt = found.weight,
totalWt = sizeWeight
})
end
end
end
return resultArray
end
function p.getItemArchSources(itemName)
local item = Items.getItem(itemName)
return p._getItemArchSources(item)
end
--[[
-- Uncomment this block and execute 'p.test()' within the debug console
-- to test after making changes
function p.test()
local checkItems = {
-- "Circlet of Rhaelyx",
-- "Jewel of Rhaelyx",
-- "Signet Ring Half (a)",
-- "Signet Ring Half (b)",
-- "Astrology Lesser Relic",
"Mysterious Stone",
"Charge Stone of Rhaelyx",
"Gold Topaz Ring",
"Charcoal",
"Ash",
"Coal Ore",
"Golden Star",
"Potion Box III",
"Rune Essence",
"Dragonite Bar",
"Holy Dust",
-- "Rune Platebody",
"Arrow Shafts",
-- "Yew Longbow",
"Blood Rune",
"Steam Rune",
-- "Wolf",
"Fox",
"Leprechaun",
"Void Wisp",
-- "Redwood Logs",
-- "Shadow Raven Nest",
"Raw Shrimp",
"Shrimp",
"Carrot Cake",
-- "Carrot Cake (Perfect)",
-- "Mantalyme Herb",
"Carrot",
"Controlled Heat Potion II",
"Topaz",
"Oricha",
"Nightopal",
-- "Sanguine Blade",
-- "Ring of Power",
-- "Infernal Claw",
-- "Chapeau Noir",
"Stardust",
"Golden Stardust",
"Abyssal Stardust",
"Rope",
"Ancient Ring of Mastery",
"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",
"Magic Bones",
"Bowstring",
"Superior Max Skillcape",
"Abyssal Coin Contract II",
"Dark Summon Consumable II",
"Abyssal Slayer Gear Upgrade Kit",
"Topaz Bolts (Enchanted)",
"Summoning Shard (Black)",
"Dragon Javelin",
"Skillers Body",
"Abyssal Compost",
}
local checkFuncs = {
p.getItemSourceTables,
--p.getCreationTable,
--p.getItemSources,
--p.getItemLootSourceTable,
}
local errCount = 0
for i, item in ipairs(checkItems) do
local param = {args={item}}
mw.log('==' .. item .. '==')
for j, func in ipairs(checkFuncs) do
local callSuccess, retVal = pcall(func, param)
if not callSuccess then
errCount = errCount + 1
mw.log('Error with item "' .. item .. '": ' .. retVal)
else
mw.log(retVal)
end
end
end
if errCount == 0 then
mw.log('Test successful')
else
mw.log('Test failed with ' .. errCount .. ' failures')
end
end
--]]
return p