Module:Items/SourceTables: Difference between revisions
From Melvor Idle
(Alt Magic -> Alt. Magic for consistency) |
(Update for v1.1) |
||
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') | ||
Line 15: | Line 12: | ||
local Monsters = require('Module:Monsters') | local Monsters = require('Module:Monsters') | ||
local Skills = require('Module:Skills') | local Skills = require('Module:Skills') | ||
function p._getCreationTable(item) | function p._getCreationTable(item) | ||
Line 38: | Line 24: | ||
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' }, | |||
if | ['Farming'] = { recipeKey = 'recipes' } | ||
lvl = | }, | ||
xp = | ['Artisan'] = { | ||
qty = 1 | ['Cooking'] = { }, | ||
time = | ['Smithing'] = { }, | ||
['Fletching'] = { }, | |||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime)) | ['Crafting'] = { }, | ||
['Runecrafting'] = { }, | |||
['Herblore'] = { }, | |||
['Summoning'] = { } | |||
} | |||
} | |||
-- Gathering skills | |||
-- All follow a similar data structure | |||
for localSkillID, dataProp in ipairs(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 | |||
-- 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 | |||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq)) | |||
-- Assumes item has a single source per skill | |||
break | |||
end | end | ||
end | |||
end | |||
if | -- Artisan skills | ||
lvl = | -- Allow follow a similar data structure | ||
xp = | for localSkillID, dataProp in ipairs(skillIDs.Artisan) do | ||
qty = | local skillData = SkillData[localSkillID] | ||
time = | 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 (localSkillID == 'Cooking' and recipe.perfectCookID == 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 | |||
-- Materials & output quantity | |||
-- Special case for Summoning recipes | |||
if localSkillID == 'Summoning' then | |||
local shardCostArray, otherCostArray = {}, {} | |||
-- | local recipeGPCost = skillData.recipeGPCost | ||
for | -- 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 | ||
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 | 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 | ||
end | end | ||
req = table.concat(shardCostArray, ', ') | |||
if not Shared.tableIsEmpty(otherCostArray) then | |||
req = req .. '<br/>and one of the following:<br/>' .. table.concat(otherCostArray, "<br/>'''OR''' ") | |||
table. | |||
end | end | ||
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time)) | |||
-- | -- Some items (such as Arrow shafts) have multiple recipes | ||
local | 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.qty .. 'x ?????') | |||
else | |||
table.insert(reqSubPart, Icons.Icon({reqItem.name, type='item', qty=itemCost.qty})) | |||
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 Gems and Bars | -- Alt. Magic, excludes spells which can produce a variety of items, such as Gems and Bars | ||
-- Bars are handled by getItemSuperheatTable() | -- Bars are handled by getItemSuperheatTable() | ||
-- Gems are handled by _getItemLootSourceTable() | -- Gems are handled by _getItemLootSourceTable() | ||
for i, altSpell in ipairs( | for i, altSpell in ipairs(p.getSpellsBySpellBook('altMagic')) do | ||
if | if altSpell.produces == item.id then | ||
table.insert(tables, p._buildAltMagicTable(altSpell)) | table.insert(tables, p._buildAltMagicTable(altSpell)) | ||
end | 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 243: | Line 192: | ||
function p.getAltMagicTable(frame) | function p.getAltMagicTable(frame) | ||
local spellName = frame.args ~= nil and frame.args[1] or frame | local spellName = frame.args ~= nil and frame.args[1] or frame | ||
local spell = Magic.getSpell(spellName, ' | local spell = Magic.getSpell(spellName, 'altMagic') | ||
if spell == nil then | if spell == nil then | ||
return 'ERROR: Could not find Alt. Magic spell "' .. spellName .. '"[[Category:Pages with script errors]]' | return 'ERROR: Could not find Alt. Magic spell "' .. spellName .. '"[[Category:Pages with script errors]]' | ||
Line 253: | Line 202: | ||
function p._buildAltMagicTable(spell) | function p._buildAltMagicTable(spell) | ||
local resultPart = {} | local resultPart = {} | ||
local imgType = p._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 = p._getAltSpellCostText(spell) | |||
if costText ~= nil then | |||
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials') | |||
table.insert(resultPart, '\r\n| ' .. costText) | |||
if | |||
end | end | ||
Line 305: | Line 229: | ||
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 343: | Line 263: | ||
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 and maxTime > time 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 367: | Line 287: | ||
--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._getMonsterBones(monster) ~= nil then | if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then | ||
Line 378: | Line 298: | ||
-- - 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 391: | Line 311: | ||
-- 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 j, itemRewardID in ipairs(event.itemRewardIDs) do | |||
if item.id == itemRewardID then | |||
local dungPrefix = (i == Shared.tableCount(event.itemRewardIDs) and '' or (i == 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 Shared. | if not Shared.tableIsEmpty(dungeonStrPart) then | ||
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ',')) | table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ',')) | ||
end | end | ||
if Shared. | if not Shared.tableIsEmpty(killStrPart) then | ||
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ',')) | table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ',')) | ||
end | end | ||
-- | -- Can we find it in an openable item? | ||
local lootPart = {} | |||
local lootPart | for i, item2 in ipairs(GameData.rawData.items) do | ||
for i, item2 in | |||
if item2.dropTable ~= nil then | if item2.dropTable ~= nil then | ||
for j, loot in ipairs(item2.dropTable) do | for j, loot in ipairs(item2.dropTable) do | ||
if loot | if loot.id == item.id then | ||
table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true})) | table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true})) | ||
break | break | ||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
if | |||
if not Shared.tableIsEmpty(lootPart) then | |||
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, ',')) | table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, ',')) | ||
end | 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 | end | ||
if | |||
local upgradeCat = false | |||
for k, 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 = (k == 'up' and 'Upgrading') or 'Downgrading' | |||
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, ',')) | |||
end | |||
end | end | ||
Line 467: | Line 398: | ||
--Check if we can make it ourselves | --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 | |||
if recipe | for localSkillID, dataProp in ipairs(skillIDs.Gathering) do | ||
local skillData = SkillData[localSkillID] | |||
local skill = skillData.name | |||
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do | |||
if recipe.productId == item.id then | |||
table.insert(lineArray, Icons._SkillReq(skill, recipe.level)) | |||
break | |||
end | end | ||
end | end | ||
end | end | ||
-- | -- Artisan skills | ||
for | for localSkillID, dataProp in ipairs(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) then | |||
table.insert(lineArray, Icons._SkillReq(skill, recipe.level)) | |||
break | |||
end | |||
end | end | ||
end | end | ||
-- | --AstrologyCheck (Just a brute force for now because only two items) | ||
if item. | 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: Nests | ||
if | -- TODO Ash | ||
table.insert(lineArray, Icons. | if item.id == SkillData.Woodcutting.nestItemID then | ||
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, 1)) | |||
for i, | elseif item.id == SkillData.Woodcutting.ravenNestItemID then | ||
if | 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 | ||
end | end | ||
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, levelReq)) | |||
end | end | ||
-- | -- Fishing: Junk & Specials | ||
if item. | if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then | ||
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]') | |||
elseif GameData.getEntityByProperty(SkillData.Fishing.specialItems, 'itemID', item.id) ~= nil then | |||
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]') | |||
end | end | ||
-- | -- Firemaking: Coal | ||
-- TODO Ash, Charcoal, Diamonds, Fire spirits | |||
if item.id == SkillData.Firemaking.coalItemID then | |||
table.insert(lineArray, Icons._SkillReq("Firemaking", 1)) | |||
end | end | ||
-- | -- Mining: Gems | ||
if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or | |||
if item. | GameData.getEntityByProperty('randomSuperiorGems', 'itemID', item.id) ~= nil) then | ||
table.insert(lineArray, Icons. | table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]') | ||
end | end | ||
-- | -- Alt. Magic | ||
if | if not Shared.tableIsEmpty(Magic.getSpellsProducingItem(item.id)) then | ||
table.insert(lineArray, Icons.Icon({" | table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'})) | ||
end | end | ||
--Rhaelyx pieces | --Finally there are some weird exceptions: | ||
--Rhaelyx pieces | |||
if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then | if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then | ||
local rhaSkills = { | local rhaSkills = { | ||
Line 599: | Line 510: | ||
--Tokens are from the appropriate skill | --Tokens are from the appropriate skill | ||
if item. | if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then | ||
table.insert(lineArray, Icons._SkillReq( | for localSkillID, skillData in ipairs(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 | ||
Line 609: | Line 525: | ||
--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 625: | Line 540: | ||
--Gold Topaz Ring drops from any action (when not wearing a Gold Topaz Ring) | --Gold Topaz Ring drops from any action (when not wearing a Gold Topaz Ring) | ||
--Also handling Signet Ring things here | --Also handling Signet Ring things here | ||
if item. | if item.id == 'melvorD:Gold_Topaz_Ring' then | ||
table.insert(lineArray, 'Any non-combat action if not worn (Instead of '..Icons.Icon({"Signet Ring Half (a)", type="item"})..')') | table.insert(lineArray, 'Any non-combat action if not worn (Instead of '..Icons.Icon({"Signet Ring Half (a)", type="item"})..')') | ||
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')') | table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')') | ||
elseif item. | elseif item.id == 'melvorD:Signet_Ring_Half_A' then | ||
table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'})) | table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'})) | ||
elseif 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'})) | table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'})) | ||
end | end | ||
Line 679: | Line 594: | ||
-- 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 691: | Line 606: | ||
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 698: | Line 613: | ||
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 706: | Line 621: | ||
--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) | ||
if monster ~= nil then | |||
table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = drop.minQty, drop.maxQty, weight = drop.dropWt, totalWeight = drop.totalWt}) | |||
if monster | |||
end | end | ||
end | |||
-- Is the item dropped from any dungeon? | -- Is the item dropped from any dungeon? | ||
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'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1}) | |||
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 j, itemRewardID in ipairs(event.itemRewardIDs) do | |||
if item.id == itemRewardID then | |||
local sourceTxt = Icons.Icon({dungeon.name, type='dungeon', notext=true}) .. (i == Shared.tableCount(event.itemRewardIDs) and '' or ', Cycle ' .. i) | |||
table.insert(dropRows, {source = sourceTxt, type = '[[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 ipairs( | 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.id == item.id then | ||
totalWt = totalWt + loot | wt = loot.weight | ||
if loot | minQty = loot.minQuantity | ||
wt = loot | maxQty = loot.maxQuantity | ||
end | end | ||
end | end | ||
Line 775: | Line 663: | ||
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, type = '[[Chest]]', minqty = | table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = minQty, qty = maxQty, weight = wt, totalWeight = totalWt}) | ||
end | end | ||
end | end | ||
end | end | ||
-- | -- Can it be obtained from Thieving? | ||
local thiefItems = Skills.getThievingSourcesForItem(item.id) | local thiefItems = Skills.getThievingSourcesForItem(item.id) | ||
for i, thiefRow in ipairs(thiefItems) do | for i, thiefRow in ipairs(thiefItems) do | ||
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, type = | table.insert(dropRows, {source = sourceTxt, type = Icons.Icon({SkillData.Thieving.name, type='skill'}), minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt}) | ||
end | end | ||
-- | -- Fishing: Junk & Specials | ||
if Shared.contains(SkillData.Fishing. | if Shared.contains(SkillData.Fishing.junkItems, 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'}) | ||
Line 801: | Line 688: | ||
else | else | ||
local fishTotWeight, fishItem = 0, nil | local fishTotWeight, fishItem = 0, nil | ||
for i, specialItem in ipairs(SkillData.Fishing. | for i, specialItem in ipairs(SkillData.Fishing.specialItems) do | ||
if specialItem | if specialItem.itemID == item.id then | ||
fishItem = specialItem | fishItem = specialItem | ||
end | end | ||
fishTotWeight = fishTotWeight + specialItem | fishTotWeight = fishTotWeight + specialItem.weight | ||
end | end | ||
if fishItem ~= nil then | if fishItem ~= nil then | ||
local fishSource = '[[Fishing#Special|Special]]' | local fishSource = '[[Fishing#Special|Special]]' | ||
local fishType = Icons.Icon({ | local fishType = Icons.Icon({SkillData.Fishing.name, type='skill'}) | ||
table.insert(dropRows, {source = fishSource, type = fishType, minqty = fishItem | table.insert(dropRows, {source = fishSource, type = fishType, minqty = fishItem.minQuantity, qty = fishItem.maxQuantity, weight = fishItem.weight, totalWeight = fishTotWeight}) | ||
end | end | ||
end | end | ||
-- | |||
if | -- Mining: Gems, and also Alt. Magic spells producing random gems | ||
local | 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[gemKey]) do | |||
totalGemWeight = totalGemWeight + gem.weight | |||
if gem.itemID == item.id then | |||
thisGem = gem | |||
end | |||
end | |||
if thisGem ~= nil then | |||
local mineType = Icons.Icon({SkillData.Mining.name, type='skill'}) | |||
table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight}) | |||
-- Check for Alt. Magic spells also | |||
local magicType = Icons.Icon({SkillData.Magic.name, type = 'skill'}) | |||
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=p._getSpellIconType(spell)}), type = magicType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight}) | |||
end | |||
end | |||
end | end | ||
end | end | ||
Line 831: | Line 729: | ||
--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) | ||
Line 844: | Line 742: | ||
end | end | ||
end) | end) | ||
for i, data in | for i, data in ipairs(dropRows) do | ||
table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight)) | table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight)) | ||
end | end | ||
Line 864: | Line 762: | ||
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 type( | 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 897: | Line 793: | ||
return p._getItemUpgradeTable(item) | return p._getItemUpgradeTable(item) | ||
end | |||
function p._getSuperheatSmithRecipe(item) | |||
local smithRecipe = GameData.getEntityByID(SkillData.Smithing.recipes, 'productID', item.id) | |||
if smithRecipe ~= nil and smithRecipe.category == 'melvorD:Bars' then | |||
return smithRecipe | |||
end | |||
end | end | ||
Line 902: | Line 805: | ||
--Manually build the Superheat Item table | --Manually build the Superheat Item table | ||
-- Validate that the item can be superheated | -- Validate that the item can be superheated | ||
local | local smithRecipe = p._getSuperheatSmithRecipe(item.id) | ||
if smithRecipe == nil then | |||
return 'ERROR: The item "' .. item.name .. '" cannot be superheated[[Category:Pages with script errors]]' | return 'ERROR: The item "' .. item.name .. '" cannot be superheated[[Category:Pages with script errors]]' | ||
end | end | ||
Line 919: | Line 812: | ||
local oreStringPart, coalString = {}, '' | local oreStringPart, coalString = {}, '' | ||
for i, mat in ipairs(smithRecipe.itemCosts) do | for i, mat in ipairs(smithRecipe.itemCosts) do | ||
local | local matItem = Items.getItemByID(mat.id) | ||
if | if mat.id == 'melvorD:Coal_Ore' then | ||
coalString = Icons.Icon({ | coalString = Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity}) | ||
else | else | ||
table.insert(oreStringPart, Icons.Icon({ | 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 934: | Line 828: | ||
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 = p._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 | ||
end | end | ||
--Add the table end and add the table to the result string | --Add the table end and add the table to the result string | ||
table.insert(superheatTable, '\r\n|}') | table.insert(superheatTable, '\r\n|}') | ||
Line 967: | Line 864: | ||
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 | if p._getSuperheatSmithRecipe(item.id) ~= 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 1,015: | Line 909: | ||
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 1,032: | Line 925: | ||
function p._getItemMonsterSources(item) | function p._getItemMonsterSources(item) | ||
local resultArray = {} | local resultArray = {} | ||
for i, monster in ipairs( | for i, monster in ipairs(GameData.rawData.monsters) do | ||
local chance = 0 | local chance = 0 | ||
local weight = 0 | local weight = 0 | ||
Line 1,039: | Line 932: | ||
if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then | if monster.bones == 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 | ||
maxQty = (monster.bones.quantity ~= nil and monster.bones.quantity) or 1 | |||
minQty = maxQty | |||
chance = 1 | chance = 1 | ||
weight = 1 | weight = 1 | ||
elseif monster.lootTable ~= nil and not Monsters._isDungeonOnlyMonster(monster) then | |||
elseif monster.lootTable ~= nil 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 | ||
-- Dungeon exclusive monsters don't count as they are either: | -- Dungeon exclusive monsters don't count as they are either: | ||
Line 1,051: | Line 942: | ||
-- - 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 | ||
weight = weight + loot | weight = weight + loot.weight | ||
if loot | if loot.itemID == item.id then | ||
chance = loot | chance = loot.weight | ||
maxQty = loot | minQty = loot.minQuantity | ||
maxQty = loot.maxQuantity | |||
end | end | ||
end | end | ||
local lootChance = monster.lootChance ~= nil and monster.lootChance or 100 | local lootChance = monster.lootChance ~= nil and monster.bones ~= item.id and monster.lootChance or 100 | ||
chance = chance * lootChance | chance = chance * lootChance | ||
weight = weight * 100 | weight = weight * 100 |
Revision as of 15:51, 22 October 2022
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 GameData = require('Module:GameData')
local SkillData = GameData.skillData
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')
function p._getCreationTable(item)
local skill = ''
local specialReq = nil
local time = 0
local maxTime = nil
local lvl = 0
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' }
},
['Artisan'] = {
['Cooking'] = { },
['Smithing'] = { },
['Fletching'] = { },
['Crafting'] = { },
['Runecrafting'] = { },
['Herblore'] = { },
['Summoning'] = { }
}
}
-- Gathering skills
-- All follow a similar data structure
for localSkillID, dataProp in ipairs(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
-- 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
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, 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 ipairs(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 (localSkillID == 'Cooking' and recipe.perfectCookID == 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
-- 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 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}))
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
req = req .. '<br/>and one of the following:<br/>' .. table.concat(otherCostArray, "<br/>'''OR''' ")
end
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
-- Some items (such as Arrow shafts) have multiple recipes
elseif type(recipe.alternativeCosts) == 'table' then
local reqPart, qtyPart = {}, {}
for j, altCost in ipairs(recipe.alternativeCosts) do
local reqSubPart = {}
for k, itemCost in ipairs(altCost.itemCosts) do
local reqItem = Items.getItemByID(itemCost.id)
if reqItem == nil then
table.insert(reqSubPart, itemCost.qty .. 'x ?????')
else
table.insert(reqSubPart, Icons.Icon({reqItem.name, type='item', qty=itemCost.qty}))
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
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
-- 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(p.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 'ERROR: Could not find Alt. Magic spell "' .. spellName .. '"[[Category:Pages with script errors]]'
else
return p._buildAltMagicTable(spell)
end
end
function p._buildAltMagicTable(spell)
local resultPart = {}
local imgType = p._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 = p._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, xp, req, qty, time, maxTime, specialReq, gpCost, scCost)
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))
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 pairs(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.qty..'x ?????')
else
table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat.qty}))
end
end
if gpCost ~= nil and gpCost > 0 then
table.insert(resultPart, '<br/>')
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
else
table.insert(resultPart, req)
end
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|'..Shared.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 "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
end
return p._getCreationTable(item)
end
function p._getItemSources(item, asList, addCategories)
local lineArray = {}
local categoryArray = {}
--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 == 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.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
table.insert(killStrPart, Icons.Icon({monster.name, type='monster', notext=true}))
end
end
-- Is the item dropped from any dungeon?
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}))
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 j, itemRewardID in ipairs(event.itemRewardIDs) do
if item.id == itemRewardID then
local dungPrefix = (i == Shared.tableCount(event.itemRewardIDs) and '' or (i == 1 and 'cycle' or 'cycles') .. ' of ')
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({dungeon.name, type='dungeon', notext=true}))
break
end
end
end
end
end
if not Shared.tableIsEmpty(dungeonStrPart) then
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ','))
end
if not Shared.tableIsEmpty(killStrPart) then
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ','))
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.id == 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, ','))
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 k, 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 = (k == 'up' and 'Upgrading') or 'Downgrading'
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, ','))
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 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))
else
table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
end
end
if #thiefPart > 0 then
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, ','))
end
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 ipairs(skillIDs.Gathering) do
local skillData = SkillData[localSkillID]
local skill = skillData.name
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
if recipe.productId == item.id then
table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
break
end
end
end
-- Artisan skills
for localSkillID, dataProp in ipairs(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) then
table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
break
end
end
end
--AstrologyCheck (Just a brute force for now because only two items)
if Shared.contains({SkillData.Astrology.stardustItemID, SkillData.Astrology.goldenStardustItemID}, item.id) then
table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'}))
end
-- Woodcutting: Nests
-- TODO Ash
if item.id == SkillData.Woodcutting.nestItemID then
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, 1))
elseif 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))
end
-- Fishing: Junk & Specials
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
elseif GameData.getEntityByProperty(SkillData.Fishing.specialItems, 'itemID', item.id) ~= nil then
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]')
end
-- Firemaking: Coal
-- TODO Ash, Charcoal, Diamonds, Fire spirits
if item.id == SkillData.Firemaking.coalItemID then
table.insert(lineArray, Icons._SkillReq("Firemaking", 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]]')
end
-- Alt. Magic
if not Shared.tableIsEmpty(Magic.getSpellsProducingItem(item.id)) then
table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
end
--Finally there are some weird exceptions:
--Rhaelyx pieces
if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then
local rhaSkills = {
Circlet = {'Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility', 'Astrology'},
Jewel = {'Firemaking', 'Cooking', 'Smithing', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Summoning'}
}
local rhaSkList, subText = nil, ''
if item.name == 'Circlet of Rhaelyx' then
rhaSkList = {rhaSkills.Circlet}
elseif item.name == 'Jewel of Rhaelyx' then
rhaSkList = {rhaSkills.Jewel}
elseif item.name == 'Mysterious Stone' then
rhaSkList = {rhaSkills.Jewel, rhaSkills.Circlet}
subText = '<br/>after finding ' .. Icons.Icon({'Crown of Rhaelyx', type='item'})
end
local rhaStrPart = {}
for i, skillList in ipairs(rhaSkList) do
for j, skillName in ipairs(skillList) do
table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
end
end
table.insert(lineArray, 'Any action in: ' .. table.concat(rhaStrPart, ', ') .. subText)
end
--Tokens are from the appropriate skill
if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then
for localSkillID, skillData in ipairs(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
--Gold Topaz Ring drops from any action (when not wearing a Gold Topaz Ring)
--Also handling Signet Ring things here
if item.id == 'melvorD:Gold_Topaz_Ring' then
table.insert(lineArray, 'Any non-combat action if not worn (Instead of '..Icons.Icon({"Signet Ring Half (a)", type="item"})..')')
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_A' then
table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', 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'}))
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 "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
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!!Source Type!!Quantity!!colspan="2"|Chance')
--Set up function for adding rows
local buildRow = function(source, type, minqty, qty, weight, totalWeight)
if minqty == nil then minqty = 1 end
local rowPart = {}
table.insert(rowPart, '\r\n|-')
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
table.insert(rowPart, '\r\n|style="text-align: left;"|'..type)
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
-- 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 = Shared.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 .. '"| ' .. Shared.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)
if monster ~= nil then
table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = drop.minQty, drop.maxQty, weight = drop.dropWt, totalWeight = drop.totalWt})
end
end
-- Is the item dropped from any dungeon?
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'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
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 j, itemRewardID in ipairs(event.itemRewardIDs) do
if item.id == itemRewardID then
local sourceTxt = Icons.Icon({dungeon.name, type='dungeon', notext=true}) .. (i == Shared.tableCount(event.itemRewardIDs) and '' or ', Cycle ' .. i)
table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
break
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.id == 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, type = '[[Chest]]', minqty = minQty, qty = maxQty, weight = wt, totalWeight = totalWt})
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
table.insert(dropRows, {source = sourceTxt, type = Icons.Icon({SkillData.Thieving.name, type='skill'}), minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt})
end
-- Fishing: Junk & Specials
if Shared.contains(SkillData.Fishing.junkItems, item.id) then
local fishSource = '[[Fishing#Junk|Junk]]'
local fishType = Icons.Icon({'Fishing', type='skill'})
local fishTotWeight = Shared.tableCount(SkillData.Fishing.JunkItems)
table.insert(dropRows, {source = fishSource, type = fishType, 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, type = fishType, minqty = fishItem.minQuantity, qty = fishItem.maxQuantity, weight = fishItem.weight, totalWeight = fishTotWeight})
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[gemKey]) do
totalGemWeight = totalGemWeight + gem.weight
if gem.itemID == item.id then
thisGem = gem
end
end
if thisGem ~= nil then
local mineType = Icons.Icon({SkillData.Mining.name, type='skill'})
table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight})
-- Check for Alt. Magic spells also
local magicType = Icons.Icon({SkillData.Magic.name, type = 'skill'})
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=p._getSpellIconType(spell)}), type = magicType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight})
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.type == b.type and a.source < b.source) or a.type < b.type
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.type, data.minqty, data.qty, data.weight, data.totalWeight))
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 "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
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 = {}
for i, itemCost in ipairs(upgrade.itemCosts) do
local costItem = Items.getItemByID(itemCost.id)
if costItem ~= nil then
table.insert(upgradeCost, Icons.Icon({costItem.name, type='item', qty=itemCost.quantity}))
end
end
if type(upgrade.gpCost) == 'number' and upgrade.gpCost > 0 then
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
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, table.concat(upgradeCost, '<br/>'))
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 "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
end
return p._getItemUpgradeTable(item)
end
function p._getSuperheatSmithRecipe(item)
local smithRecipe = GameData.getEntityByID(SkillData.Smithing.recipes, 'productID', item.id)
if smithRecipe ~= nil and smithRecipe.category == '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.id)
if smithRecipe == nil then
return 'ERROR: The item "' .. item.name .. '" cannot be superheated[[Category:Pages with script errors]]'
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 = p._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 "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
end
return p._getItemSuperheatTable(item)
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
if p._getSuperheatSmithRecipe(item.id) ~= 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 "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
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 Constants.getModifiersText(item.modifiers, false)
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 == 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.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 ~= 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
--[==[
-- Uncomment this block and execute 'p.test()' within the debug console
-- to test after making changes
function p.test()
local checkItems = {
'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',
'Mysterious Stone',
'Mastery Token (Cooking)',
'Gem Gloves',
"Thief's Moneysack"
}
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