Module:Items/SourceTables: Difference between revisions
From Melvor Idle
Falterfire (talk | contribs) m (Changed Lost Chest level to acquire to 100 since it can't be obtained until buying the Corundum Fishing Rod at level 100 Fishing) |
(Allow Harvesting to show up in ItemBox Item Sources) |
||
(28 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local Constants = require('Module:Constants') | local Constants = require('Module:Constants') | ||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local Common = require('Module:Common') | |||
local GameData = require('Module:GameData') | local GameData = require('Module:GameData') | ||
local SkillData = GameData.skillData | local SkillData = GameData.skillData | ||
local Modifiers = require('Module:Modifiers') | |||
local Magic = require('Module:Magic') | local Magic = require('Module:Magic') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
Line 12: | Line 13: | ||
local Monsters = require('Module:Monsters') | local Monsters = require('Module:Monsters') | ||
local Skills = require('Module:Skills') | 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, nil | |||
elseif type(recipe.products) == 'table' then | |||
for i, product in ipairs(recipe.products) do | |||
if product.itemID == itemID then | |||
local specialReq = nil | |||
if recipe.products[i].minIntensityPercent ~= nil then | |||
specialReq = recipe.products[i].minIntensityPercent .. '% Intensity' | |||
end | |||
return true, specialReq | |||
end | |||
end | |||
end | |||
return false, nil | |||
end | |||
function p._getCreationTable(item) | function p._getCreationTable(item) | ||
local skill = '' | local skill = '' | ||
local specialReq = nil | local specialReq = nil | ||
local category = nil | |||
local time = 0 | local time = 0 | ||
local maxTime = nil | local maxTime = nil | ||
local lvl = 0 | local lvl = 0 | ||
local isAbyssal = false | |||
local xp = 0 | local xp = 0 | ||
local qty = nil | local qty = nil | ||
Line 32: | Line 57: | ||
['Fishing'] = { recipeKey = 'fish' }, | ['Fishing'] = { recipeKey = 'fish' }, | ||
['Mining'] = { recipeKey = 'rockData' }, | ['Mining'] = { recipeKey = 'rockData' }, | ||
['Farming'] = { recipeKey = 'recipes' } | ['Farming'] = { recipeKey = 'recipes' }, | ||
['Harvesting'] = { recipeKey = 'veinData' } | |||
}, | }, | ||
['Artisan'] = { | ['Artisan'] = { | ||
Line 43: | Line 69: | ||
['Summoning'] = { } | ['Summoning'] = { } | ||
} | } | ||
} | |||
-- Gathering skills | -- Gathering skills | ||
Line 50: | Line 76: | ||
local skillData = SkillData[localSkillID] | local skillData = SkillData[localSkillID] | ||
local skill = skillData.name | local skill = skillData.name | ||
local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil | local lvl, isAbyssal, xp, qty, req, category, time, maxTime = 0, false, 0, 0, nil, nil, 0, nil | ||
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do | for i, recipe in ipairs(skillData[dataProp.recipeKey]) do | ||
local hasProduct, specialReq = doesRecipeHaveItemID(recipe, itemID) | |||
lvl = recipe | if hasProduct then | ||
xp = recipe.baseExperience | lvl, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe) | ||
xp = recipe.baseAbyssalExperience or recipe.baseExperience | |||
qty = recipe.baseQuantity or 1 | qty = recipe.baseQuantity or 1 | ||
if localSkillID == 'Farming' then | if localSkillID == 'Farming' then | ||
req = { recipe.seedCost } | req = { recipe.seedCost } | ||
local catData = GameData.getEntityByID(skillData.categories, recipe.categoryID) | |||
category = catData.name | |||
qty = 5 * catData.harvestMultiplier | |||
end | end | ||
-- Action time | -- Action time | ||
Line 72: | Line 102: | ||
-- Special requirements | -- Special requirements | ||
if recipe.totalMasteryRequired ~= nil then | if recipe.totalMasteryRequired ~= nil then | ||
specialReq = Icons.Icon({'Mastery', notext=true}) .. | specialReq = Icons.Icon({'Mastery', notext=true}) .. Num.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]' | ||
end | end | ||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq)) | table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qty, category, time, maxTime, specialReq)) | ||
-- Assumes item has a single source per skill | -- Assumes item has a single source per skill | ||
break | break | ||
Line 86: | Line 116: | ||
local skillData = SkillData[localSkillID] | local skillData = SkillData[localSkillID] | ||
local skill = skillData.name | local skill = skillData.name | ||
local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil | local lvl, isAbyssal, xp, qty, category, req, time, maxTime = 0, false, 0, 0, nil, nil, 0, 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 | ||
(localSkillID == 'Cooking' and recipe.perfectCookID == itemID) or | (localSkillID == 'Cooking' and recipe.perfectCookID == itemID) or | ||
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, itemID)) then | (localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, itemID)) then | ||
lvl = recipe | lvl, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe) | ||
xp = recipe.baseExperience | xp = recipe.baseAbyssalExperience or recipe.baseExperience | ||
qty = recipe.baseQuantity or 1 | qty = recipe.baseQuantity or 1 | ||
-- Recipe Category | |||
if recipe.categoryID ~= nil then | |||
local catData = GameData.getEntityByID(SkillData[localSkillID].categories, recipe.categoryID) | |||
category = catData.modifierName or catData.name or nil | |||
end | |||
-- Action time | -- Action time | ||
if recipe.baseMinInterval ~= nil then | if recipe.baseMinInterval ~= nil then | ||
Line 117: | Line 152: | ||
if localSkillID == 'Summoning' then | if localSkillID == 'Summoning' then | ||
local shardCostArray, otherCostArray = {}, {} | local shardCostArray, otherCostArray = {}, {} | ||
local | local recipeCost = 0 | ||
if isAbyssal == true then | |||
recipeCost = skillData.recipeAPCost | |||
else | |||
recipeCost = skillData.recipeGPCost | |||
end | |||
-- Shards | -- Shards | ||
for j, itemCost in ipairs(recipe.itemCosts) do | for j, itemCost in ipairs(recipe.itemCosts) do | ||
Line 126: | Line 166: | ||
end | end | ||
-- Other costs | -- Other costs | ||
table.insert(otherCostArray, Common.getCostString({ ["items"] = {}, ["currencies"] = recipe.currencyCosts})) | |||
for j, nonShardID in ipairs(recipe.nonShardItemCosts) do | for j, nonShardID in ipairs(recipe.nonShardItemCosts) do | ||
local nonShard = Items.getItemByID(nonShardID) | local nonShard = Items.getItemByID(nonShardID) | ||
if nonShard ~= nil then | if nonShard ~= nil then | ||
local itemValue = math.max(nonShard.sellsFor, 20) | local itemValue = math.max(nonShard.sellsFor, 20) | ||
local nonShardQty = math.max(1, math. | local nonShardQty = math.max(1, math.ceil(recipeCost / itemValue)) | ||
table.insert(otherCostArray, Icons.Icon({nonShard.name, type='item', notext=true, qty=nonShardQty})) | table.insert(otherCostArray, Icons.Icon({nonShard.name, type='item', notext=true, qty=nonShardQty})) | ||
end | end | ||
Line 145: | Line 180: | ||
req = req .. '<br/>' .. (costLen == 1 and '' or 'and one of the following:<br/>') .. table.concat(otherCostArray, "<br/>'''OR''' ") | req = req .. '<br/>' .. (costLen == 1 and '' or 'and one of the following:<br/>') .. table.concat(otherCostArray, "<br/>'''OR''' ") | ||
end | end | ||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time)) | specialReq = 'At least 1 ' .. Icons.Icon({'Summoning%23Summoning Marks', item.name, img=item.name, type='mark'}) .. ' mark discovered' | ||
table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qty, category, time, nil, specialReq)) | |||
-- Some items (such as Arrow shafts) have multiple recipes | -- Some items (such as Arrow shafts) have multiple recipes | ||
elseif type(recipe.alternativeCosts) == 'table' then | elseif type(recipe.alternativeCosts) == 'table' then | ||
Line 159: | Line 195: | ||
end | end | ||
end | end | ||
table.insert(reqSubPart, Common.getCostString({ ["items"] = {}, ["currencies"] = recipe.currencyCosts})) | |||
table.insert(reqPart, table.concat(reqSubPart, ', ')) | table.insert(reqPart, table.concat(reqSubPart, ', ')) | ||
table.insert(qtyPart, | table.insert(qtyPart, Num.formatnum(qty * altCost.quantityMultiplier)) | ||
end | end | ||
local sep = "<br/>'''OR''' " | local sep = "<br/>'''OR''' " | ||
req = table.concat(reqPart, sep) | req = table.concat(reqPart, sep) | ||
local qtyText = table.concat(qtyPart, sep) | local qtyText = table.concat(qtyPart, sep) | ||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qtyText, time, maxTime, specialReq)) | table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qtyText, category, time, maxTime, specialReq)) | ||
-- 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 | ||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, recipe.itemCosts, qty, time, maxTime, specialReq, recipe. | table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, recipe.itemCosts, qty, category, time, maxTime, specialReq, recipe.currencyCosts)) | ||
end | end | ||
end | end | ||
Line 200: | Line 232: | ||
local spell = Magic.getSpell(spellName, 'altMagic') | local spell = Magic.getSpell(spellName, 'altMagic') | ||
if spell == nil then | if spell == nil then | ||
return ' | return Shared.printError('Could not find Alt. Magic spell "' .. spellName .. '"') | ||
else | else | ||
return p._buildAltMagicTable(spell) | return p._buildAltMagicTable(spell) | ||
Line 231: | Line 263: | ||
end | end | ||
function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq, | function p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qty, category, time, maxTime, specialReq, currencyCost) | ||
if qty == nil then qty = 1 end | if qty == nil then qty = 1 end | ||
local resultPart = {} | local resultPart = {} | ||
Line 237: | Line 269: | ||
table.insert(resultPart, '\r\n!colspan="2"|Item ' .. (req == nil and 'Creation' or 'Production')) | table.insert(resultPart, '\r\n!colspan="2"|Item ' .. (req == nil and 'Creation' or 'Production')) | ||
table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Requirements') | table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Requirements') | ||
table.insert(resultPart, '\r\n|'..Icons._SkillReq(skill, lvl)) | table.insert(resultPart, '\r\n|'..Icons._SkillReq(skill, lvl, false, (isAbyssal and "melvorItA:Abyssal" or nil))) | ||
if specialReq ~= nil then table.insert(resultPart, '<br/>'..specialReq) end | if specialReq ~= nil then table.insert(resultPart, '<br/>'..specialReq) end | ||
Line 252: | Line 284: | ||
end | end | ||
end | end | ||
if currencyCost ~= nil then | |||
table.insert(resultPart, Common.getCostString({ ["items"] = {}, ["currencies"] = currencyCost })) | |||
if | |||
table.insert(resultPart, | |||
end | end | ||
else | else | ||
table.insert(resultPart, req) | table.insert(resultPart, req) | ||
end | end | ||
end | |||
if category ~= nil then | |||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Recipe Category') | |||
table.insert(resultPart, '\r\n|'..category) | |||
end | end | ||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity') | table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity') | ||
table.insert(resultPart, '\r\n|'..qty) | table.insert(resultPart, '\r\n|'..qty) | ||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Experience') | table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Experience') | ||
table.insert(resultPart, '\r\n|'.. | table.insert(resultPart, '\r\n|'..Num.formatnum(xp)..' XP') | ||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Creation Time') | table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Creation Time') | ||
table.insert(resultPart, '\r\n|'..Shared.timeString(time, true)) | table.insert(resultPart, '\r\n|'..Shared.timeString(time, true)) | ||
Line 280: | Line 312: | ||
local item = Items.getItem(itemName) | local item = Items.getItem(itemName) | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 286: | Line 318: | ||
end | end | ||
function p._getItemSources(item, asList, addCategories) | function p._getItemSources(item, asList, addCategories, separator) | ||
local lineArray = {} | local lineArray = {} | ||
local categoryArray = {} | local categoryArray = {} | ||
local sep = separator or ',' | |||
--Alright, time to go through all the ways you can get an item... | --Alright, time to go through all the ways you can get an item... | ||
Line 297: | Line 330: | ||
if monster.bones ~= nil and monster.bones.itemID == item.id and Monsters._getMonsterBones(monster) ~= nil then | 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 | -- 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 | isDrop = true | ||
elseif monster.lootTable ~= nil then | elseif monster.lootTable ~= nil then | ||
Line 312: | Line 348: | ||
if isDrop then | if isDrop then | ||
-- Item drops when the monster is killed | -- Item drops when the monster is killed | ||
table.insert(killStrPart, Icons.Icon({ | 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? | -- Is the item dropped from any dungeon? | ||
local dungeonStrPart = {} | local dungeonStrPart = {} | ||
for i, dungeon in ipairs( | local dungeonEntities = { | ||
['Dungeon'] = GameData.rawData.dungeons, | |||
['The Abyss'] = GameData.rawData.abyssDepths | |||
} | |||
for entity, dungeons in pairs(dungeonEntities) do | |||
local iconType = entity == 'Dungeon' and 'dungeon' or 'combatArea' | |||
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=iconType, 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=iconType, 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 | ||
Line 337: | Line 396: | ||
if not Shared.tableIsEmpty(dungeonStrPart) then | if not Shared.tableIsEmpty(dungeonStrPart) then | ||
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, | table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, sep)) | ||
end | end | ||
if not Shared.tableIsEmpty(killStrPart) then | if not Shared.tableIsEmpty(killStrPart) then | ||
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, | table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, sep)) | ||
end | end | ||
Line 357: | Line 416: | ||
if not Shared.tableIsEmpty(lootPart) then | if not Shared.tableIsEmpty(lootPart) then | ||
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, | table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, sep)) | ||
end | end | ||
Line 382: | Line 441: | ||
end | end | ||
local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading' | local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading' | ||
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, | table.insert(lineArray, typeText .. ': ' .. table.concat(parts, sep)) | ||
end | end | ||
end | end | ||
Line 401: | Line 460: | ||
end | end | ||
if not Shared.tableIsEmpty(thiefPart) then | if not Shared.tableIsEmpty(thiefPart) then | ||
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, | table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, sep)) | ||
end | end | ||
end | end | ||
Line 411: | Line 470: | ||
end | end | ||
if not Shared.tableIsEmpty(castPart) then | if not Shared.tableIsEmpty(castPart) then | ||
table.insert(lineArray, 'Casting: ' .. table.concat(castPart, | table.insert(lineArray, 'Casting: ' .. table.concat(castPart, sep)) | ||
end | end | ||
Line 420: | Line 479: | ||
['Fishing'] = { recipeKey = 'fish' }, | ['Fishing'] = { recipeKey = 'fish' }, | ||
['Mining'] = { recipeKey = 'rockData' }, | ['Mining'] = { recipeKey = 'rockData' }, | ||
['Farming'] = { recipeKey = 'recipes' } | ['Farming'] = { recipeKey = 'recipes' }, | ||
['Harvesting'] = { recipeKey = 'veinData' } | |||
}, | }, | ||
['Artisan'] = { | ['Artisan'] = { | ||
Line 431: | Line 491: | ||
['Summoning'] = { } | ['Summoning'] = { } | ||
} | } | ||
} | |||
-- Gathering skills | -- Gathering skills | ||
Line 438: | Line 498: | ||
local skill = skillData.name | local skill = skillData.name | ||
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do | 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 | if localSkillID == 'Farming' and recipe.seedCost ~= nil then | ||
local seedItem = Items.getItemByID(recipe.seedCost.id) | local seedItem = Items.getItemByID(recipe.seedCost.id) | ||
Line 445: | Line 506: | ||
end | end | ||
else | else | ||
table.insert(lineArray, Icons._SkillReq(skill, | local level, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe) | ||
table.insert(lineArray, Icons._SkillReq(skill, level, false, (isAbyssal and "melvorItA:Abyssal" or nil))) | |||
end | end | ||
break | break | ||
Line 460: | Line 522: | ||
(localSkillID == 'Cooking' and recipe.perfectCookID == item.id) or | (localSkillID == 'Cooking' and recipe.perfectCookID == item.id) or | ||
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, item.id)) then | (localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, item.id)) then | ||
table.insert(lineArray, Icons._SkillReq(skill, recipe.level)) | 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 | break | ||
end | end | ||
Line 466: | Line 648: | ||
end | end | ||
--AstrologyCheck | --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 | end | ||
Line 494: | Line 678: | ||
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then | if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then | ||
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]') | table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]') | ||
elseif item.id == SkillData.Fishing.lostChestItem then | elseif item.id == SkillData.Fishing.lostChestItem then | ||
table.insert(lineArray, Icons._SkillReq(SkillData.Fishing.name, 100)) | 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 | end | ||
Line 523: | Line 710: | ||
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 | |||
local | -- All general rare drops within the Magic are for Alt. Magic | ||
if skillDataAll.skillID == 'melvorD:Magic' then | |||
skillName, displaySkillName = 'Alt. Magic', true | |||
end | 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 | end | ||
table.insert(lineArray, 'Any action in: ' .. table.concat( | 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 | end | ||
Line 578: | Line 804: | ||
end | 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 | end | ||
Line 612: | Line 834: | ||
end | end | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 622: | Line 844: | ||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | ||
table.insert(resultPart, '\r\n|- class="headerRow-0"') | table.insert(resultPart, '\r\n|- class="headerRow-0"') | ||
table.insert(resultPart, '\r\n!Source!! | table.insert(resultPart, '\r\n!Source!!Level!!Quantity!!colspan="2"|Chance') | ||
--Set up function for adding rows | --Set up function for adding rows | ||
local buildRow = function(source, | local buildRow = function(source, level, levelNum, minqty, qty, weight, totalWeight, expIcon) | ||
if minqty == nil then minqty = 1 end | if minqty == nil then minqty = 1 end | ||
if expIcon == nil then expIcon = '' end | if expIcon == nil then expIcon = '' end | ||
if level == nil then level = 'N/A' end | |||
local rowPart = {} | local rowPart = {} | ||
table.insert(rowPart, '\r\n|-') | table.insert(rowPart, '\r\n|-') | ||
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source) | table.insert(rowPart, '\r\n|style="text-align: left;"|'..source) | ||
-- | -- Retrieve numeric level value for sorting, or remove anything between [[]] | ||
local | local levelValue = '' | ||
if | if levelNum ~= nil then | ||
levelValue = tostring(levelNum) | |||
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'.. | else | ||
levelValue = level:match('%[%[.-%]%]%s*(%w+)$') or '' | |||
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'.. | end | ||
if qty ~= minqty then table.insert(rowPart, ' - '.. | table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon.. level) | ||
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 | local chance = weight / totalWeight * 100 | ||
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places | -- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places | ||
Line 648: | Line 873: | ||
table.insert(rowPart, '\r\n|colspan="2" ') | table.insert(rowPart, '\r\n|colspan="2" ') | ||
else | else | ||
local fraction = | local fraction = Num.fraction(weight, totalWeight) | ||
if Shared.contains(fraction, '%.') then | if Shared.contains(fraction, '%.') then | ||
--If fraction contains decimals, something screwy happened so just show only percentage | --If fraction contains decimals, something screwy happened so just show only percentage | ||
Line 654: | Line 879: | ||
table.insert(rowPart, '\r\n|colspan="2" ') | table.insert(rowPart, '\r\n|colspan="2" ') | ||
else | else | ||
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chanceStr .. '"| ' .. | table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chanceStr .. '"| ' .. Num.fraction(weight, totalWeight) .. '\r\n|') | ||
end | end | ||
end | end | ||
Line 671: | Line 896: | ||
for i, drop in ipairs(p._getItemMonsterSources(item)) do | for i, drop in ipairs(p._getItemMonsterSources(item)) do | ||
local monster = GameData.getEntityByID('monsters', drop.id) | 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 | if monster ~= nil then | ||
table.insert(dropRows, {source = Icons.Icon({monster.name, type=' | 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 | |||
--Patching in here because it uses the same format | |||
--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 | end | ||
-- Is the item dropped from any dungeon? | -- Is the item dropped from any dungeon? | ||
for i, dungeon in ipairs( | local dungeonEntities = { | ||
['Dungeon'] = GameData.rawData.dungeons, | |||
['The Abyss'] = GameData.rawData.abyssDepths | |||
} | |||
for entity, dungeons in pairs(dungeonEntities) do | |||
local iconType = entity == 'Dungeon' and 'dungeon' or 'combatArea' | |||
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=iconType}), | |||
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=iconType}) .. (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 | ||
Line 711: | Line 1,005: | ||
if wt > 0 then | if wt > 0 then | ||
local sourceTxt = Icons.Icon({item2.name, type='item'}) | local sourceTxt = Icons.Icon({item2.name, type='item'}) | ||
table.insert(dropRows, {source = sourceTxt, | table.insert(dropRows, { | ||
source = sourceTxt, | |||
level = '[[Chest]]', | |||
minqty = minQty, | |||
qty = maxQty, | |||
weight = wt, | |||
totalWeight = totalWt, | |||
expIcon = Icons.getExpansionIcon(item2.id)}) | |||
end | end | ||
end | end | ||
Line 725: | Line 1,026: | ||
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'}) | sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'}) | ||
end | end | ||
table.insert(dropRows, {source = sourceTxt, | 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 | -- Fishing: Junk & Specials | ||
if Shared.contains(SkillData.Fishing. | if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then | ||
local fishSource = '[[Fishing#Junk|Junk]]' | local fishSource = '[[Fishing#Junk|Junk]]' | ||
local fishType = Icons.Icon({'Fishing', type='skill'}) | local fishType = Icons.Icon({'Fishing', type='skill'}) | ||
local fishTotWeight = Shared.tableCount(SkillData.Fishing. | local fishTotWeight = Shared.tableCount(SkillData.Fishing.junkItemIDs) | ||
table.insert(dropRows, {source = fishSource, | table.insert(dropRows, { | ||
source = fishSource, | |||
level = Icons._SkillReq("Fishing", 1), | |||
levelNum = 1, | |||
minqty = 1, | |||
qty = 1, | |||
weight = 1, | |||
totalWeight = fishTotWeight}) | |||
else | else | ||
local fishTotWeight, fishItem = 0, nil | local fishTotWeight, fishItem, realmID = {['melvorD:Melvor'] = 0, ['melvorItA:Abyssal'] = 0}, nil, nil | ||
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do | for i, specialItem in ipairs(SkillData.Fishing.specialItems) do | ||
if | 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 | ||
end | end | ||
if fishItem ~= nil then | if fishItem ~= nil then | ||
local fishSource = '[[Fishing#Special|Special]]' | local fishSource = '[[Fishing#Special|Special]]' | ||
local fishType = Icons.Icon({SkillData.Fishing.name, type='skill'}) | local fishType = Icons.Icon({SkillData.Fishing.name, type='skill'}) | ||
table.insert(dropRows, {source = fishSource, | 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 | ||
end | end | ||
Line 761: | Line 1,089: | ||
end | end | ||
if thisGem ~= nil then | if thisGem ~= nil then | ||
local expIcon = '' | local expIcon = '' | ||
local sourceTxt | |||
local lv = nil | |||
if item.type == 'Superior Gem' then | if item.type == 'Superior Gem' then | ||
expIcon = Icons.TotH() | 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 | end | ||
table.insert(dropRows, {source = ' | 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 | -- Check for Alt. Magic spells also | ||
local producesKey = (gemKey == 'randomGems' and 'RandomGem') or 'RandomSuperiorGem' | local producesKey = (gemKey == 'randomGems' and 'RandomGem') or 'RandomSuperiorGem' | ||
for j, spell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do | for j, spell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do | ||
if spell.produces ~= nil and spell.produces == producesKey then | if spell.produces ~= nil and spell.produces == producesKey then | ||
table.insert(dropRows, {source = Icons.Icon({spell.name, type=Magic._getSpellIconType(spell)}), type = | 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 | ||
Line 786: | Line 1,137: | ||
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 | ||
return (a. | return (a.level == b.level and a.source < b.source) or a.level < b.level | ||
else | else | ||
return a.minqty + a.qty > b.minqty + b.qty | return a.minqty + a.qty > b.minqty + b.qty | ||
Line 795: | Line 1,146: | ||
end) | end) | ||
for i, data in ipairs(dropRows) do | for i, data in ipairs(dropRows) do | ||
table.insert(resultPart, buildRow(data.source, data. | table.insert(resultPart, buildRow(data.source, data.level, data.levelNum, data.minqty, data.qty, data.weight, data.totalWeight, data.expIcon)) | ||
end | end | ||
Line 806: | Line 1,157: | ||
local item = Items.getItem(itemName) | local item = Items.getItem(itemName) | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 816: | Line 1,167: | ||
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 upgradeCost = | local upgradeCost = Common.getCostString({ | ||
["items"] = upgrade.itemCosts, | |||
["currencies"] = upgrade.currencyCosts | |||
}) | |||
table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]') | table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]') | ||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|') | table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|') | ||
table.insert(resultPart, | table.insert(resultPart, upgradeCost) | ||
table.insert(resultPart, '\r\n|}') | table.insert(resultPart, '\r\n|}') | ||
end | end | ||
Line 841: | Line 1,184: | ||
local item = Items.getItem(itemName) | local item = Items.getItem(itemName) | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 859: | Line 1,202: | ||
local smithRecipe = p._getSuperheatSmithRecipe(item) | local smithRecipe = p._getSuperheatSmithRecipe(item) | ||
if smithRecipe == nil then | if smithRecipe == nil then | ||
return ' | return Shared.printError('The item "' .. item.name .. '" cannot be superheated') | ||
end | end | ||
Line 898: | Line 1,241: | ||
end | end | ||
--Add the table end and add the table to the result string | |||
table.insert(superheatTable, '\r\n|}') | table.insert(superheatTable, '\r\n|}') | ||
return table.concat(superheatTable) | return table.concat(superheatTable) | ||
Line 907: | Line 1,250: | ||
local item = Items.getItem(itemName) | local item = Items.getItem(itemName) | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
return p._getItemSuperheatTable(item) | return p._getItemSuperheatTable(item) | ||
end | |||
function p._getTownshipTraderTable(item) | |||
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, build table | |||
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 resultPart = {} | |||
table.insert(resultPart, '{| class="wikitable"\n|-') | |||
table.insert(resultPart, '\n!colspan="2"| ' .. Icons.Icon({'Township', 'Trader', type='skill'})) | |||
table.insert(resultPart, '\n|-\n!style="text-align:right;"| Cost') | |||
table.insert(resultPart, '\n| ' .. Icons.Icon({resName, qty=resQty, type='resource'})) | |||
table.insert(resultPart, '\n|-\n!style="text-align:right;| Requirements') | |||
table.insert(resultPart, '\n| ' .. Shop.getRequirementString(tradeDef.unlockRequirements)) | |||
table.insert(resultPart, '\n|}') | |||
return table.concat(resultPart) | |||
end | |||
end | |||
end | |||
return '' | |||
end | end | ||
Line 931: | Line 1,299: | ||
if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end | if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end | ||
table.insert(resultPart, upgradeTable) | table.insert(resultPart, upgradeTable) | ||
end | |||
local townshipTable = p._getTownshipTraderTable(item) | |||
if townshipTable ~= '' then | |||
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\n') end | |||
table.insert(resultPart, '===Township===\n' .. townshipTable) | |||
end | end | ||
Line 949: | Line 1,323: | ||
local item = Items.getItem(itemName) | local item = Items.getItem(itemName) | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 964: | Line 1,338: | ||
for i, item in ipairs(itemArray) do | for i, item in ipairs(itemArray) do | ||
local passiveDesc = item.customDescription or | local passiveDesc = item.customDescription or Modifiers.getModifiersText(item.modifiers, false, false, 10) | ||
table.insert(resultPart, '|-\r\n') | 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, '! '..Icons.Icon({item.name, type='item', notext='true'})..'\r\n! '..Icons.Icon({item.name, type='item', noicon=true})..'\r\n') | ||
Line 988: | Line 1,362: | ||
chance = 1 | chance = 1 | ||
weight = 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 | 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 | -- If the monster has a loot table, check if the item we are looking for is in there | ||
Line 1,004: | Line 1,383: | ||
chance = chance * lootChance | chance = chance * lootChance | ||
weight = weight * 100 | weight = weight * 100 | ||
chance, weight = | chance, weight = Num.fractionpair(chance, weight) | ||
end | end | ||
if chance > 0 then | if chance > 0 then | ||
Line 1,019: | Line 1,398: | ||
end | 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 | |||
local min = found.minQuantity | |||
local max = found.maxQuantity | |||
table.insert(resultArray, { | |||
id = digSite.id, | |||
name = digSite.name, | |||
level = digSite.level, | |||
size = sizeName, | |||
minQty = min, | |||
maxQty = max, | |||
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 | -- Uncomment this block and execute 'p.test()' within the debug console | ||
-- to test after making changes | -- to test after making changes | ||
function p.test() | function p.test() | ||
local checkItems = { | local checkItems = { | ||
"Circlet of Rhaelyx", | |||
"Jewel of Rhaelyx", | |||
"Signet Ring Half (a)", | |||
"Signet Ring Half (b)", | |||
"Gold Topaz Ring", | |||
"Astrology Lesser Relic", | |||
"Mysterious Stone", | |||
"Gold Bar", | |||
"Raw Shrimp", | |||
"Coal Ore", | |||
"Rune Platebody", | |||
"Arrow Shafts", | |||
"Yew Longbow", | |||
"Water Rune", | |||
"Steam Rune", | |||
"Controlled Heat Potion II", | |||
"Wolf", | |||
"Cyclops", | |||
"Leprechaun", | |||
"Redwood Logs", | |||
"Carrot Cake", | |||
"Carrot Cake (Perfect)", | |||
"Mantalyme Herb", | |||
"Carrot", | |||
"Topaz", | |||
"Rune Essence", | |||
"Sanguine Blade", | |||
"Ring of Power", | |||
"Infernal Claw", | |||
"Thief's Moneysack" | "Chapeau Noir", | ||
"Stardust", | |||
"Rope", | |||
"Ancient Ring of Mastery", | |||
"Mastery Token (Cooking)", | |||
"Gem Gloves", | |||
"Thief's Moneysack", | |||
"Golden Stardust", | |||
"Golden Star", | |||
"Slayer Deterer", | |||
"Paper", | |||
"Lemon", | |||
"Aranite Brush", | |||
"Barrier Dust", | |||
"Shadow Raven Nest", | |||
"Void Wisp" | |||
} | } | ||
local checkFuncs = { | local checkFuncs = { | ||
p.getItemSourceTables, | --p.getItemSourceTables, | ||
--p.getCreationTable, | --p.getCreationTable, | ||
p.getItemSources, | --p.getItemSources, | ||
--p.getItemLootSourceTable, | --p.getItemLootSourceTable, | ||
} | } | ||
Line 1,081: | Line 1,516: | ||
end | end | ||
end | end | ||
--] | --]] | ||
return p | return p |
Latest revision as of 17:54, 24 November 2024
Documentation for this module may be created at Module:Items/SourceTables/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:Shop')
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, nil
elseif type(recipe.products) == 'table' then
for i, product in ipairs(recipe.products) do
if product.itemID == itemID then
local specialReq = nil
if recipe.products[i].minIntensityPercent ~= nil then
specialReq = recipe.products[i].minIntensityPercent .. '% Intensity'
end
return true, specialReq
end
end
end
return false, nil
end
function p._getCreationTable(item)
local skill = ''
local specialReq = nil
local category = nil
local time = 0
local maxTime = nil
local lvl = 0
local isAbyssal = false
local xp = 0
local qty = nil
local req = nil
local tables = {}
local itemID = item.id
--First figure out what skill is used to make this...
local skillIDs = {
['Gathering'] = {
['Woodcutting'] = { recipeKey = 'trees' },
['Fishing'] = { recipeKey = 'fish' },
['Mining'] = { recipeKey = 'rockData' },
['Farming'] = { recipeKey = 'recipes' },
['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, isAbyssal, xp, qty, req, category, time, maxTime = 0, false, 0, 0, nil, nil, 0, nil
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
local hasProduct, specialReq = doesRecipeHaveItemID(recipe, itemID)
if hasProduct then
lvl, isAbyssal = Skills.getRecipeLevelRealm(localSkillID, recipe)
xp = recipe.baseAbyssalExperience or recipe.baseExperience
qty = recipe.baseQuantity or 1
if localSkillID == 'Farming' then
req = { recipe.seedCost }
local catData = GameData.getEntityByID(skillData.categories, recipe.categoryID)
category = catData.name
qty = 5 * catData.harvestMultiplier
end
-- 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
if recipe.totalMasteryRequired ~= nil then
specialReq = Icons.Icon({'Mastery', notext=true}) .. Num.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
end
table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qty, category, time, maxTime, specialReq))
-- Assumes item has a single source per skill
break
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, isAbyssal, xp, qty, category, req, time, maxTime = 0, false, 0, 0, nil, nil, 0, 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
-- Recipe Category
if recipe.categoryID ~= nil then
local catData = GameData.getEntityByID(SkillData[localSkillID].categories, recipe.categoryID)
category = catData.modifierName or catData.name or nil
end
-- 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
specialReq = 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', notext=true, 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', notext=true, qty=nonShardQty}))
end
end
req = table.concat(shardCostArray, ', ')
if not Shared.tableIsEmpty(otherCostArray) then
local costLen = Shared.tableCount(otherCostArray)
req = req .. '<br/>' .. (costLen == 1 and '' or 'and one of the following:<br/>') .. table.concat(otherCostArray, "<br/>'''OR''' ")
end
specialReq = 'At least 1 ' .. Icons.Icon({'Summoning%23Summoning Marks', item.name, img=item.name, type='mark'}) .. ' mark discovered'
table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qty, category, time, nil, specialReq))
-- 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
local reqSubPart = {}
for k, itemCost in ipairs(altCost.itemCosts) do
local reqItem = Items.getItemByID(itemCost.id)
if reqItem == nil then
table.insert(reqSubPart, itemCost.quantity .. 'x ?????')
else
table.insert(reqSubPart, Icons.Icon({reqItem.name, type='item', qty=itemCost.quantity}))
end
end
table.insert(reqSubPart, Common.getCostString({ ["items"] = {}, ["currencies"] = recipe.currencyCosts}))
table.insert(reqPart, table.concat(reqSubPart, ', '))
table.insert(qtyPart, Num.formatnum(qty * altCost.quantityMultiplier))
end
local sep = "<br/>'''OR''' "
req = table.concat(reqPart, sep)
local qtyText = table.concat(qtyPart, sep)
table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qtyText, category, time, maxTime, specialReq))
-- Finally, normal recipes with a single set of item costs
elseif type(recipe.itemCosts) == 'table' and not Shared.tableIsEmpty(recipe.itemCosts) then
table.insert(tables, p.buildCreationTable(skill, lvl, isAbyssal, xp, recipe.itemCosts, qty, category, time, maxTime, specialReq, recipe.currencyCosts))
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 == item.id then
table.insert(tables, p._buildAltMagicTable(altSpell))
end
end
if Shared.tableIsEmpty(tables) then
return ''
else
return table.concat(tables, '\r\n')
end
end
function p.getAltMagicTable(frame)
local spellName = frame.args ~= nil and frame.args[1] or frame
local spell = Magic.getSpell(spellName, 'altMagic')
if spell == nil then
return Shared.printError('Could not find Alt. Magic spell "' .. spellName .. '"')
else
return p._buildAltMagicTable(spell)
end
end
function p._buildAltMagicTable(spell)
local resultPart = {}
local imgType = Magic._getSpellIconType(spell)
table.insert(resultPart, '{|class="wikitable"\r\n|-')
table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type=imgType}))
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements')
table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level))
local costText = Magic._getAltSpellCostText(spell)
if costText ~= nil then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
table.insert(resultPart, '\r\n| ' .. costText)
end
--Add runes
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n| ' .. Magic._getSpellRunes(spell))
--Now just need the output quantity, xp, and casting time (which is always 2)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity\r\n|' .. spell.productionRatio)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base XP\r\n|' .. spell.baseExperience)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Cast Time\r\n|2s')
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
function p.buildCreationTable(skill, lvl, isAbyssal, xp, req, qty, category, time, maxTime, specialReq, currencyCost)
if qty == nil then qty = 1 end
local resultPart = {}
table.insert(resultPart, '{|class="wikitable"')
table.insert(resultPart, '\r\n!colspan="2"|Item ' .. (req == nil and 'Creation' or 'Production'))
table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Requirements')
table.insert(resultPart, '\r\n|'..Icons._SkillReq(skill, lvl, false, (isAbyssal and "melvorItA:Abyssal" or nil)))
if specialReq ~= nil then table.insert(resultPart, '<br/>'..specialReq) end
if req ~= nil then
table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Materials\r\n|')
if type(req) == 'table' then
for i, mat in ipairs(req) do
if i > 1 then table.insert(resultPart, '<br/>') end
local matItem = Items.getItemByID(mat.id)
if matItem == nil then
table.insert(resultPart, mat.quantity..'x ?????')
else
table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat.quantity}))
end
end
if currencyCost ~= nil then
table.insert(resultPart, Common.getCostString({ ["items"] = {}, ["currencies"] = currencyCost }))
end
else
table.insert(resultPart, req)
end
end
if category ~= nil then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Recipe Category')
table.insert(resultPart, '\r\n|'..category)
end
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity')
table.insert(resultPart, '\r\n|'..qty)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Experience')
table.insert(resultPart, '\r\n|'..Num.formatnum(xp)..' XP')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Creation Time')
table.insert(resultPart, '\r\n|'..Shared.timeString(time, true))
if maxTime ~= nil and maxTime > time then table.insert(resultPart, ' - '..Shared.timeString(maxTime, true)) end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
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._getCreationTable(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
local iconType = entity == 'Dungeon' and 'dungeon' or 'combatArea'
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=iconType, 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=iconType, 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' },
['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) 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"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!Source!!Level!!Quantity!!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|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
--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
--Patching in here because it uses the same format
--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
-- 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
local iconType = entity == 'Dungeon' and 'dungeon' or 'combatArea'
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=iconType}),
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=iconType}) .. (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
-- Mining: Gems, and also Alt. Magic spells producing random gems
if Shared.contains({'Gem', 'Superior Gem'}, item.type) then
local gemKeys = { 'randomGems', 'randomSuperiorGems' }
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
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 'RandomSuperiorGem'
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
--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._getItemUpgradeTable(item)
local resultPart = {}
local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id)
if upgrade ~= nil then
local upgradeCost = Common.getCostString({
["items"] = upgrade.itemCosts,
["currencies"] = upgrade.currencyCosts
})
table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
table.insert(resultPart, upgradeCost)
table.insert(resultPart, '\r\n|}')
end
return table.concat(resultPart)
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._getItemUpgradeTable(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._getItemSuperheatTable(item)
--Manually build the Superheat Item table
-- 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 = {}, ''
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', notext='true', qty=mat.quantity})
else
table.insert(oreStringPart, Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity}))
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
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)
table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type=imgType, notext=true, size=50}))
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
table.insert(superheatTable, (not Shared.tableIsEmpty(oreStringPart) and ', ' or '') .. coalString)
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(superheatTable, '\r\n|}')
return table.concat(superheatTable)
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._getItemSuperheatTable(item)
end
function p._getTownshipTraderTable(item)
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, build table
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 resultPart = {}
table.insert(resultPart, '{| class="wikitable"\n|-')
table.insert(resultPart, '\n!colspan="2"| ' .. Icons.Icon({'Township', 'Trader', type='skill'}))
table.insert(resultPart, '\n|-\n!style="text-align:right;"| Cost')
table.insert(resultPart, '\n| ' .. Icons.Icon({resName, qty=resQty, type='resource'}))
table.insert(resultPart, '\n|-\n!style="text-align:right;| Requirements')
table.insert(resultPart, '\n| ' .. Shop.getRequirementString(tradeDef.unlockRequirements))
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
end
end
return ''
end
function p._getItemSourceTables(item)
local resultPart = {}
local shopTable = Shop._getItemShopTable(item)
if shopTable ~= '' then
table.insert(resultPart, '===Shop===\r\n'..shopTable)
end
local creationTable = p._getCreationTable(item)
if creationTable ~= '' then
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
table.insert(resultPart, '===Creation===\r\n'..creationTable)
end
local upgradeTable = p._getItemUpgradeTable(item)
if upgradeTable ~= '' then
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end
table.insert(resultPart, upgradeTable)
end
local townshipTable = p._getTownshipTraderTable(item)
if townshipTable ~= '' then
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\n') end
table.insert(resultPart, '===Township===\n' .. townshipTable)
end
if p._getSuperheatSmithRecipe(item) ~= nil then
table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt. Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
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
local min = found.minQuantity
local max = found.maxQuantity
table.insert(resultArray, {
id = digSite.id,
name = digSite.name,
level = digSite.level,
size = sizeName,
minQty = min,
maxQty = max,
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)",
"Gold Topaz Ring",
"Astrology Lesser Relic",
"Mysterious Stone",
"Gold Bar",
"Raw Shrimp",
"Coal Ore",
"Rune Platebody",
"Arrow Shafts",
"Yew Longbow",
"Water Rune",
"Steam Rune",
"Controlled Heat Potion II",
"Wolf",
"Cyclops",
"Leprechaun",
"Redwood Logs",
"Carrot Cake",
"Carrot Cake (Perfect)",
"Mantalyme Herb",
"Carrot",
"Topaz",
"Rune Essence",
"Sanguine Blade",
"Ring of Power",
"Infernal Claw",
"Chapeau Noir",
"Stardust",
"Rope",
"Ancient Ring of Mastery",
"Mastery Token (Cooking)",
"Gem Gloves",
"Thief's Moneysack",
"Golden Stardust",
"Golden Star",
"Slayer Deterer",
"Paper",
"Lemon",
"Aranite Brush",
"Barrier Dust",
"Shadow Raven Nest",
"Void Wisp"
}
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