1,280
edits
(_getItemLootSourceTable: Amend display of very small chances) |
m (Add custom separator for ItemSources) |
||
(49 intermediate revisions by 7 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 GameData = require('Module:GameData') | |||
local SkillData = GameData.skillData | |||
local Magic = require('Module:Magic') | local Magic = require('Module:Magic') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Shop = require('Module:Shop') | local Shop = require('Module:Shop') | ||
local Monsters = require('Module:Monsters') | local Monsters = require('Module:Monsters') | ||
local | local Skills = require('Module:Skills') | ||
local SourceOverrides = { | |||
['melvorAoD:EarthGolem'] = 'Earth Golem (AoD)' | |||
local | |||
[' | |||
} | } | ||
Line 36: | Line 27: | ||
local tables = {} | local tables = {} | ||
local itemID = item.id | |||
--First figure out what skill is used to make this... | --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' } | |||
}, | |||
local | ['Artisan'] = { | ||
skill = | ['Cooking'] = { }, | ||
lvl | ['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, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil | |||
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do | |||
if recipe.productId == itemID then | |||
lvl = recipe.level | |||
xp = recipe.baseExperience | |||
qty = recipe.baseQuantity or 1 | |||
if localSkillID == 'Farming' then | |||
req = { recipe.seedCost } | |||
local category = GameData.getEntityByID(skillData.categories, recipe.categoryID) | |||
if | qty = 5 * category.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}) .. Shared.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, xp, req, qty, time)) | -- Assumes item has a single source per skill | ||
break | break | ||
end | end | ||
end | |||
end | |||
-- Artisan skills | |||
-- Allow follow a similar data structure | |||
for localSkillID, dataProp in pairs(skillIDs.Artisan) do | |||
local skillData = SkillData[localSkillID] | |||
local skill = skillData.name | |||
local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil | |||
for i, recipe in ipairs(skillData.recipes) do | |||
if recipe.productID == itemID or | |||
time = | (localSkillID == 'Cooking' and recipe.perfectCookID == itemID) or | ||
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, itemID)) then | |||
lvl = recipe.level | |||
xp = recipe.baseExperience | |||
qty = recipe.baseQuantity or 1 | |||
-- 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 | 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 | end | ||
-- Materials & output quantity | |||
-- Special case for Summoning recipes | |||
if localSkillID == 'Summoning' then | |||
local shardCostArray, otherCostArray = {}, {} | |||
local recipeGPCost = skillData.recipeGPCost | |||
-- 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})) | |||
local | end | ||
end | |||
-- Other costs | |||
if recipe.gpCost > 0 then | |||
table.insert(otherCostArray, Icons.GP(recipe.gpCost)) | |||
end | |||
if recipe.scCost > 0 then | |||
table.insert(otherCostArray, Icons.SC(recipe.scCost)) | |||
end | |||
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.floor(recipeGPCost / 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 | 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, xp, req, qty, time, nil, specialReq)) | |||
table.insert( | -- 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 | |||
if recipe.gpCost ~= nil and recipe.gpCost > 0 then | |||
table.insert(reqSubPart, Icons.GP(recipe.GPCost)) | |||
end | |||
if recipe.scCost ~= nil and recipe.scCost > 0 then | |||
table.insert(reqSubPart, Icons.SC(recipe.SCCost)) | |||
end | |||
table.insert(reqPart, table.concat(reqSubPart, ', ')) | |||
table.insert(qtyPart, Shared.formatnum(qty * altCost.quantityMultiplier)) | |||
end | end | ||
local sep = "<br/>'''OR''' " | |||
req = table.concat(reqPart, sep) | |||
local qtyText = table.concat(qtyPart, sep) | |||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qtyText, 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, xp, recipe.itemCosts, qty, time, maxTime, specialReq, recipe.gpCost, recipe.scCost)) | |||
end | end | ||
end | end | ||
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 | end | ||
if Shared. | if Shared.tableIsEmpty(tables) then | ||
return | return '' | ||
else | else | ||
return table.concat(tables, '\r\n') | return table.concat(tables, '\r\n') | ||
Line 223: | Line 202: | ||
end | end | ||
function p. | function p.getAltMagicTable(frame) | ||
local spell = Magic.getSpell(spellName, ' | 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 resultPart = {} | ||
local imgType = Magic._getSpellIconType(spell) | |||
table.insert(resultPart, '{|class="wikitable"\r\n|-') | table.insert(resultPart, '{|class="wikitable"\r\n|-') | ||
table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type= | 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|-\r\n!style="text-align:right;"|Requirements') | ||
table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level)) | table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level)) | ||
local costText = Magic._getAltSpellCostText(spell) | |||
if | if costText ~= nil then | ||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials') | table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials') | ||
table.insert(resultPart, '\r\n| | table.insert(resultPart, '\r\n| ' .. costText) | ||
end | end | ||
--Add runes | --Add runes | ||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n|' | 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) | --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. | 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. | 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|-\r\n!style="text-align:right;"|Cast Time\r\n|2s') | ||
table.insert(resultPart, '\r\n|}') | table.insert(resultPart, '\r\n|}') | ||
Line 260: | Line 237: | ||
end | end | ||
function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq, gpCost) | function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq, gpCost, scCost) | ||
if qty == nil then qty = 1 end | if qty == nil then qty = 1 end | ||
local resultPart = {} | local resultPart = {} | ||
table.insert(resultPart, '{|class="wikitable"') | 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|-\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)) | ||
Line 276: | Line 249: | ||
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|') | ||
if type(req) == 'table' then | if type(req) == 'table' then | ||
for i, mat in | for i, mat in ipairs(req) do | ||
if i > 1 then table.insert(resultPart, '<br/>') end | if i > 1 then table.insert(resultPart, '<br/>') end | ||
local matItem = Items.getItemByID(mat.id) | local matItem = Items.getItemByID(mat.id) | ||
if matItem == nil then | if matItem == nil then | ||
table.insert(resultPart, mat. | table.insert(resultPart, mat.quantity..'x ?????') | ||
else | else | ||
table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat. | table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat.quantity})) | ||
end | end | ||
end | end | ||
Line 288: | Line 261: | ||
table.insert(resultPart, '<br/>') | table.insert(resultPart, '<br/>') | ||
table.insert(resultPart, Icons.GP(gpCost)) | table.insert(resultPart, Icons.GP(gpCost)) | ||
end | |||
if scCost ~= nil and scCost > 0 then | |||
table.insert(resultPart, '<br/>') | |||
table.insert(resultPart, Icons.SC(scCost)) | |||
end | end | ||
else | else | ||
Line 298: | Line 275: | ||
table.insert(resultPart, '\r\n|'..Shared.formatnum(xp)..' XP') | table.insert(resultPart, '\r\n|'..Shared.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. | table.insert(resultPart, '\r\n|'..Shared.timeString(time, true)) | ||
if maxTime ~= nil then table.insert(resultPart, ' - '..Shared. | if maxTime ~= nil and maxTime > time then table.insert(resultPart, ' - '..Shared.timeString(maxTime, true)) end | ||
table.insert(resultPart, '\r\n|}') | table.insert(resultPart, '\r\n|}') | ||
Line 309: | Line 286: | ||
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 315: | Line 292: | ||
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... | ||
--First up: Can we kill somebody and take theirs? | --First up: Can we kill somebody and take theirs? | ||
local killStrPart = {} | local killStrPart = {} | ||
for i, monster in ipairs( | for i, monster in ipairs(GameData.rawData.monsters) do | ||
local isDrop = false | local isDrop = false | ||
if monster.bones == item.id and Monsters. | 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 333: | Line 314: | ||
-- - A boss monster, whose drops are accounted for in data from Areas instead | -- - A boss monster, whose drops are accounted for in data from Areas instead | ||
for j, loot in ipairs(monster.lootTable) do | for j, loot in ipairs(monster.lootTable) do | ||
if loot | if loot.itemID == item.id and not Monsters._isDungeonOnlyMonster(monster) then | ||
isDrop = true | isDrop = true | ||
break | break | ||
Line 341: | Line 322: | ||
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(GameData.rawData.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='dungeon', notext=true})) | table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', 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='dungeon', notext=true})) | |||
break | |||
end | |||
end | |||
end | |||
end | end | ||
end | end | ||
if not Shared.tableIsEmpty(dungeonStrPart) then | |||
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, sep)) | |||
end | end | ||
if not Shared.tableIsEmpty(killStrPart) then | |||
if | table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, sep)) | ||
table.insert( | |||
end | end | ||
if | -- 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 | end | ||
if Shared. | |||
table.insert(lineArray, ' | if not Shared.tableIsEmpty(lootPart) then | ||
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, sep)) | |||
end | end | ||
-- | -- Is the item a result of upgrading/downgrading another item? | ||
local upgradePart = { up = {}, down = {} } | |||
local | 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 | |||
for i, | table.insert(upgradePart[key], Icons.Icon({rootItem.name, type='item', notext=true})) | ||
if | |||
for j, | |||
end | end | ||
end | 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]]') | table.insert(categoryArray, '[[Category:Upgraded Items]]') | ||
upgradeCat = true | |||
end | end | ||
local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading' | |||
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, sep)) | |||
end | end | ||
end | |||
--Next: Can we take it from somebody else -without- killing them? | |||
local thiefItems = Skills.getThievingSourcesForItem(item.id) | |||
table.insert( | 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 | ||
end | end | ||
if | if not Shared.tableIsEmpty(thiefPart) then | ||
table.insert(lineArray, | table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, sep)) | ||
end | end | ||
end | end | ||
-- Can we get this item by casting an Alt. Magic spell? | |||
if | 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' } | |||
}, | |||
['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 | |||
for i, | if recipe.productId == item.id then | ||
if | 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 | |||
table.insert(lineArray, Icons._SkillReq(skill, recipe.level)) | |||
end | |||
break | |||
end | end | ||
end | 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 | |||
table.insert(lineArray, Icons._SkillReq(skill, recipe.level)) | |||
break | |||
end | |||
end | |||
end | end | ||
-- | -- Township trading | ||
if item. | 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 | end | ||
-- | -- Archaeology sources | ||
if item. | -- 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 | end | ||
-- | -- Cartography | ||
if item. | -- 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 | end | ||
-- POI discovery rewards | |||
-- | for i, worldMap in ipairs(SkillData.Cartography.worldMaps) do | ||
if item. | 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 | end | ||
-- Travel events | |||
-- | for i, event in ipairs(SkillData.Cartography.travelEvents) do | ||
if | 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 | end | ||
-- | --AstrologyCheck (Just a brute force for now because only two items) | ||
if | if Shared.contains({SkillData.Astrology.stardustItemID, SkillData.Astrology.goldenStardustItemID}, item.id) then | ||
table.insert(lineArray, Icons. | table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'})) | ||
end | end | ||
-- | -- Woodcutting | ||
if | -- Raven Nest | ||
table.insert(lineArray, Icons. | if item.id == SkillData.Woodcutting.ravenNestItemID then | ||
local levelReq = nil | |||
table.insert(lineArray, Icons._SkillReq( | 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 | end | ||
-- | -- Fishing | ||
if item. | -- Junk | ||
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then | |||
table.insert(lineArray, Icons._SkillReq( | table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]') | ||
-- Specials | |||
elseif GameData.getEntityByProperty(SkillData.Fishing.specialItems, 'itemID', item.id) ~= nil then | |||
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]') | |||
elseif item.id == SkillData.Fishing.lostChestItem then | |||
table.insert(lineArray, Icons._SkillReq(SkillData.Fishing.name, 100)) | |||
end | end | ||
-- | -- Firemaking: Coal | ||
if | if Shared.contains({SkillData.Firemaking.coalItemID, | ||
SkillData.Firemaking.ashItemID, | |||
SkillData.Firemaking.charcoalItemID, | |||
table.insert(lineArray, Icons._SkillReq( | SkillData.Firemaking.fireSpiritItemID, | ||
SkillData.Firemaking.diamondItemID | |||
}, item.id) then | |||
table.insert(lineArray, Icons._SkillReq(SkillData.Firemaking.name, 1)) | |||
end | end | ||
-- | -- Mining: Gems | ||
if item. | if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or | ||
table.insert(lineArray, Icons. | 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 | 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 | end | ||
if not Shared.tableIsEmpty(skillIconList) then | |||
table.insert(lineArray, 'Any action in: ' .. table.concat(skillIconList, ', ') .. subText) | |||
if | skillIconList, subText = {}, '' | ||
table.insert(lineArray, | |||
end | end | ||
-- | -- Supplementary stuff on top of general rare drops | ||
if item. | if item.id == 'melvorD:Gold_Topaz_Ring' then | ||
table.insert(lineArray, Icons.Icon({ | 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 | ||
-- | --Tokens are from the appropriate skill | ||
if | 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 | end | ||
end | end | ||
-- | -- Golbin Raid exclusive items | ||
if item. | if item.golbinRaidExclusive then | ||
table.insert(lineArray, Icons. | table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'})) | ||
end | end | ||
--Shop items (including special items like gloves that aren't otherwise listed) | --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'})) | table.insert(lineArray, Icons.Icon({'Shop'})) | ||
end | end | ||
Line 566: | Line 749: | ||
table.insert(lineArray, '[[Events]]') | table.insert(lineArray, '[[Events]]') | ||
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 598: | Line 780: | ||
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 608: | Line 790: | ||
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 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) | ||
table.insert(rowPart, '\r\n|style="text-align: left;"|'.. | -- Retrieve numeric level value for sorting, or remove anything between [[]] | ||
local levelValue = '' | |||
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..minqty) | if levelNum ~= nil then | ||
if qty ~= minqty then table.insert(rowPart, ' - '..qty) end | 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..'"|'..Shared.formatnum(minqty)) | |||
if qty ~= minqty then table.insert(rowPart, ' - '..Shared.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 | ||
local fmt = (chance < 0.10 and '%.2g') or '%.2f' | local fmt = (chance < 0.10 and '%.2g') or '%.2f' | ||
local chanceStr = string.format(fmt, chance) | |||
if weight >= totalWeight then | if weight >= totalWeight then | ||
-- Fraction would be 1/1, so only show the percentage | -- Fraction would be 1/1, so only show the percentage | ||
chanceStr = '100' | |||
table.insert(rowPart, '\r\n|colspan="2" ') | table.insert(rowPart, '\r\n|colspan="2" ') | ||
else | else | ||
Line 635: | Line 825: | ||
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="' .. | table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chanceStr .. '"| ' .. Shared.fraction(weight, totalWeight) .. '\r\n|') | ||
end | end | ||
end | end | ||
Line 642: | Line 832: | ||
table.insert(rowPart, 'style="text-align: right;" data-sort-value="0"|Varies (see Thieving page)') | table.insert(rowPart, 'style="text-align: right;" data-sort-value="0"|Varies (see Thieving page)') | ||
else | else | ||
table.insert(rowPart, 'style="text-align: right;" data-sort-value="'.. | table.insert(rowPart, 'style="text-align: right;" data-sort-value="'.. chanceStr .. '"|'..chanceStr..'%') | ||
end | end | ||
return table.concat(rowPart) | return table.concat(rowPart) | ||
Line 650: | Line 840: | ||
--Alright, time to go through a few ways to get the item | --Alright, time to go through a few ways to get the item | ||
--First up: Can we kill somebody and take theirs? | --First up: Can we kill somebody and take theirs? | ||
for i, | for i, drop in ipairs(p._getItemMonsterSources(item)) do | ||
local | local monster = GameData.getEntityByID('monsters', drop.id) | ||
local iconName = monster.name | |||
if SourceOverrides[drop.id] ~= nil then | |||
iconName = SourceOverrides[drop.id] | |||
end | end | ||
if | if monster ~= nil then | ||
local monsterLevel = Monsters._getMonsterCombatLevel(monster) | |||
table.insert(dropRows, {source = Icons.Icon({ | table.insert(dropRows, { | ||
source = Icons.Icon({iconName, type='monster'}), | |||
level = Icons.Icon({'Monsters', img='Combat', notext=true}) .. ' Level ' .. Shared.formatnum(monsterLevel), | |||
levelNum = monsterLevel, | |||
minqty = drop.minQty, | |||
qty = drop.maxQty, | |||
weight = drop.dropWt, | |||
totalWeight = drop.totalWt, | |||
expIcon = Icons.getExpansionIcon(drop.id)}) | |||
end | end | ||
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 | |||
table.insert(dropRows, {source = Icons.Icon({ | 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 | |||
for i, | -- Is the item dropped from any dungeon? | ||
if item.id == | for i, dungeon in ipairs(GameData.rawData.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='dungeon'}), | |||
level = '[[Dungeon]]', | |||
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='dungeon'}) .. (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or ', Cycle ' .. eventCycle) | |||
table.insert(dropRows, { | |||
source = sourceTxt, | |||
level = '[[Dungeon]]', | |||
minqty = 1, | |||
qty = 1, | |||
weight = 1, | |||
totalWeight = 1}) | |||
break | |||
end | |||
end | |||
end | |||
end | end | ||
end | end | ||
-- | -- Can we find it in an openable item? | ||
for i, item2 in | for i, item2 in ipairs(GameData.rawData.items) do | ||
if item2.dropTable ~= nil then | if item2.dropTable ~= nil then | ||
local | local minQty, maxQty, wt, totalWt = 1, 1, 0, 0 | ||
for j, loot in ipairs(item2.dropTable) do | |||
totalWt = totalWt + loot.weight | |||
for j, loot in | if loot.itemID == item.id then | ||
totalWt = totalWt + loot | wt = loot.weight | ||
if loot | minQty = loot.minQuantity | ||
wt = loot | maxQty = loot.maxQuantity | ||
end | end | ||
end | end | ||
Line 719: | Line 925: | ||
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 | ||
end | end | ||
-- | -- Can it be obtained from Thieving? | ||
local thiefItems = Skills.getThievingSourcesForItem(item.id) | |||
local thiefItems = | for i, thiefRow in ipairs(thiefItems) do | ||
for i, thiefRow in | |||
local sourceTxt = '' | local sourceTxt = '' | ||
if thiefRow.npc == 'all' then | if thiefRow.npc == 'all' then | ||
sourceTxt = | sourceTxt = 'Thieving Rare Drop' | ||
else | else | ||
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'}) | sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'}) | ||
end | end | ||
table.insert(dropRows, {source = sourceTxt, | table.insert(dropRows, { | ||
source = sourceTxt, | |||
level = Icons._SkillReq("Thieving", thiefRow.level), | |||
levelNum = thiefRow.level, | |||
minqty = thiefRow.minQty, | |||
qty = thiefRow.maxQty, | |||
weight = thiefRow.wt, | |||
totalWeight = thiefRow.totalWt, | |||
expIcon = Icons.getExpansionIcon(thiefRow.npcID)}) | |||
end | end | ||
-- | -- Fishing: Junk & Specials | ||
if Shared.contains(SkillData.Fishing.junkItems, item.id) then | |||
if | local fishSource = '[[Fishing#Junk|Junk]]' | ||
local | local fishType = Icons.Icon({'Fishing', type='skill'}) | ||
local | local fishTotWeight = Shared.tableCount(SkillData.Fishing.JunkItems) | ||
local | table.insert(dropRows, { | ||
for i, | source = fishSource, | ||
level = Icons._SkillReq("Fishing", 1), | |||
levelNum = 1, | |||
minqty = 1, | |||
qty = 1, | |||
weight = 1, | |||
totalWeight = fishTotWeight}) | |||
else | |||
local fishTotWeight, fishItem = 0, nil | |||
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do | |||
if specialItem.itemID == item.id then | |||
fishItem = specialItem | |||
end | |||
fishTotWeight = fishTotWeight + specialItem.weight | |||
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), | |||
levelNum = 1, | |||
minqty = fishItem.minQuantity, | |||
qty = fishItem.maxQuantity, | |||
weight = fishItem.weight, | |||
totalWeight = fishTotWeight}) | |||
end | end | ||
end | end | ||
if item. | -- 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 | end | ||
--Make sure to return nothing if there are no drop sources | --Make sure to return nothing if there are no drop sources | ||
if Shared. | if Shared.tableIsEmpty(dropRows) then return '' end | ||
table.sort(dropRows, function(a, b) | table.sort(dropRows, function(a, b) | ||
if a.weight / a.totalWeight == b.weight / b.totalWeight then | if a.weight / a.totalWeight == b.weight / b.totalWeight then | ||
return a.minqty + a.qty > b.minqty + b.qty | 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 | else | ||
return a.weight / a.totalWeight > b.weight / b.totalWeight | return a.weight / a.totalWeight > b.weight / b.totalWeight | ||
end | end | ||
end) | end) | ||
for i, data in | 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 786: | Line 1,072: | ||
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 794: | Line 1,080: | ||
function p._getItemUpgradeTable(item) | function p._getItemUpgradeTable(item) | ||
local resultPart = {} | local resultPart = {} | ||
local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id) | |||
if upgrade ~= nil then | |||
local upgradeCost = {} | |||
local | for i, itemCost in ipairs(upgrade.itemCosts) do | ||
for i, | local costItem = Items.getItemByID(itemCost.id) | ||
local | if costItem ~= nil then | ||
table.insert(upgradeCost, Icons.Icon({costItem.name, type='item', qty=itemCost.quantity})) | |||
table.insert( | |||
end | end | ||
end | end | ||
if | if type(upgrade.gpCost) == 'number' and upgrade.gpCost > 0 then | ||
table.insert( | table.insert(upgradeCost, Icons.GP(upgrade.gpCost)) | ||
end | |||
if type(upgrade.scCost) == 'number' and upgrade.scCost > 0 then | |||
table.insert(upgradeCost, Icons.SC(upgrade.scCost)) | |||
end | end | ||
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.concat( | table.insert(resultPart, table.concat(upgradeCost, '<br/>')) | ||
table.insert(resultPart, '\r\n|}') | table.insert(resultPart, '\r\n|}') | ||
end | end | ||
Line 823: | Line 1,107: | ||
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._getItemUpgradeTable(item) | 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 | end | ||
function p._getItemSuperheatTable(item) | function p._getItemSuperheatTable(item) | ||
--Manually build the Superheat Item table | --Manually build the Superheat Item table | ||
local | -- Validate that the item can be superheated | ||
local coalString = '' | local smithRecipe = p._getSuperheatSmithRecipe(item) | ||
for i, mat in | if smithRecipe == nil then | ||
local | return Shared.printError('The item "' .. item.name .. '" cannot be superheated') | ||
if | end | ||
coalString = | |||
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 | else | ||
table.insert(oreStringPart, Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity})) | |||
end | end | ||
end | end | ||
--Set up the header | --Set up the header | ||
local superheatTable = {} | local superheatTable = {} | ||
Line 850: | Line 1,146: | ||
table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars') | table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars') | ||
table.insert(superheatTable, '!!Ore!!Runes') | table.insert(superheatTable, '!!Ore!!Runes') | ||
local | --Loop through all the variants | ||
for i, | 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) | |||
if | end | ||
table.insert(superheatTable, '||style="text-align:center"| ' .. Magic._getSpellRunes(spell)) | |||
end | end | ||
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 883: | Line 1,173: | ||
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 892: | Line 1,207: | ||
local resultPart = {} | local resultPart = {} | ||
local shopTable = Shop._getItemShopTable(item) | local shopTable = Shop._getItemShopTable(item) | ||
if | if shopTable ~= '' then | ||
table.insert(resultPart, '===Shop===\r\n'..shopTable) | table.insert(resultPart, '===Shop===\r\n'..shopTable) | ||
end | end | ||
local creationTable = p._getCreationTable(item) | local creationTable = p._getCreationTable(item) | ||
if | if creationTable ~= '' then | ||
if | if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end | ||
table.insert(resultPart, '===Creation===\r\n'..creationTable) | table.insert(resultPart, '===Creation===\r\n'..creationTable) | ||
end | end | ||
local upgradeTable = p._getItemUpgradeTable(item) | local upgradeTable = p._getItemUpgradeTable(item) | ||
if | if upgradeTable ~= '' then | ||
if | if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end | ||
if | if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end | ||
table.insert(resultPart, upgradeTable) | table.insert(resultPart, upgradeTable) | ||
end | end | ||
if | local townshipTable = p._getTownshipTraderTable(item) | ||
table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(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 | end | ||
local lootTable = p._getItemLootSourceTable(item) | local lootTable = p._getItemLootSourceTable(item) | ||
if | if lootTable ~= '' then | ||
if | if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end | ||
table.insert(resultPart, '===Loot===\r\n'..lootTable) | table.insert(resultPart, '===Loot===\r\n'..lootTable) | ||
end | end | ||
Line 925: | Line 1,246: | ||
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 937: | Line 1,258: | ||
table.insert(resultPart, '!colspan="2"|Item\r\n! Passive\r\n') | table.insert(resultPart, '!colspan="2"|Item\r\n! Passive\r\n') | ||
local itemArray = Items.getItems(function(item) return item.validSlots ~= nil and Shared.contains(item.validSlots, 'Passive') | 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 | for i, item in ipairs(itemArray) do | ||
local passiveDesc = item.customDescription or Constants.getModifiersText(item.modifiers, false) | |||
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') | ||
table.insert(resultPart, '| '.. | table.insert(resultPart, '| '..passiveDesc..'\r\n') | ||
end | end | ||
Line 951: | Line 1,271: | ||
return table.concat(resultPart) | return table.concat(resultPart) | ||
end | 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 = Shared.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" | |||
} | |||
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 | return p |
edits