Difference between revisions of "Module:Items/SourceTables"
From Melvor Idle
(Use Module:Skills instead of Module:Skills/Gathering) |
(Alt Magic -> Alt. Magic for consistency) |
||
Line 245: | Line 245: | ||
local spell = Magic.getSpell(spellName, 'AltMagic') | 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]]' |
else | else | ||
return p._buildAltMagicTable(spell) | return p._buildAltMagicTable(spell) | ||
Line 258: | Line 258: | ||
table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level)) | table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level)) | ||
− | -- The produces property of Alt | + | -- The produces property of Alt. Magic spells is as follows: |
-- -3 = A random gem, using the same weights as Mining | -- -3 = A random gem, using the same weights as Mining | ||
-- -2 = A bar of the type being created (Superheat) | -- -2 = A bar of the type being created (Superheat) | ||
Line 266: | Line 266: | ||
-- The amount produced is determined by the productionRatio property | -- The amount produced is determined by the productionRatio property | ||
− | -- The consumes property of Alt Magic spells is as follows: | + | -- The consumes property of Alt. Magic spells is as follows: |
-- 0 = Any item | -- 0 = Any item | ||
-- 1 = Junk item | -- 1 = Junk item | ||
Line 987: | Line 987: | ||
local recipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1] | local recipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1] | ||
if recipe ~= nil and recipe.category == 0 then | if recipe ~= nil and recipe.category == 0 then | ||
− | table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item)) | + | table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt. Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item)) |
end | end | ||
end | end |
Latest revision as of 16:23, 21 May 2022
Documentation for this module may be created at Module:Items/SourceTables/doc
local p = {} local MonsterData = mw.loadData('Module:Monsters/data') local ItemData = mw.loadData('Module:Items/data') local SkillData = mw.loadData('Module:Skills/data') local MagicData = mw.loadData('Module:Magic/data') local Constants = require('Module:Constants') local Shared = require('Module:Shared') local Magic = require('Module:Magic') local Areas = require('Module:CombatAreas') local Icons = require('Module:Icons') local Items = require('Module:Items') local Shop = require('Module:Shop') local Monsters = require('Module:Monsters') local Skills = require('Module:Skills') local SkillEnum = mw.loadData('Module:Constants/data').skill -- Implements overrides for sources which cannot be obtained from game data -- Currently only overrides for dungeon sources are implemented here local sourceOverrides = { ['Dungeon'] = { [950] = 'Volcanic Cave', -- A Tale of the Past, a future's prophecy [951] = 'Fire God Dungeon', -- The First Hero and an Unknown Evil [1116] = 'Into the Mist' -- Beginning of the End } } 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 = {} --First figure out what skill is used to make this... if type(item.masteryID) == 'table' then local skillID, masteryID = item.masteryID[1], item.masteryID[2] skill = Constants.getSkillName(skillID) if skillID == SkillEnum.Fishing then -- Fishing local fish = SkillData.Fishing.Fish[masteryID + 1] if fish ~= nil and fish.itemID == item.id then lvl = fish.level xp = fish.baseXP qty = 1 time = fish.baseMinInterval / 1000 maxTime = fish.baseMaxInterval / 1000 table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime)) end elseif skillID == SkillEnum.Mining then -- Mining local rock = SkillData.Mining.Rocks[masteryID + 1] if rock ~= nil then lvl = rock.levelRequired xp = rock.baseExperience qty = rock.baseQuantity time = 3 if item.name == 'Dragonite Ore' then specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]' end table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq)) end elseif Shared.contains({SkillEnum.Smithing, SkillEnum.Fletching, SkillEnum.Crafting, SkillEnum.Runecrafting, SkillEnum.Herblore}, skillID) then -- Smithing, Fletching, Crafting, Runecrafting, Herblore -- All have somewhat consistent recipe data structures local recipeKey = (skillID == SkillEnum.Herblore and 'Potions') or 'Recipes' local recipe = SkillData[skill][recipeKey][masteryID + 1] if recipe ~= nil then local masteryReq = nil local itemMatch = (recipe.itemID == item.id) if skillID == SkillEnum.Herblore then -- For Herblore, we need to check a table of potion IDs & determine the mastery requirement for i, potionID in ipairs(recipe.potionIDs) do if potionID == item.id then itemMatch = true masteryReq = SkillData.Herblore.TierMasteryLevels[i] break end end end if itemMatch then local baseTime = { [SkillEnum.Smithing] = 2, [SkillEnum.Fletching] = 2, [SkillEnum.Crafting] = 3, [SkillEnum.Runecrafting] = 2, [SkillEnum.Herblore] = 2 } local baseQty = recipe.baseQuantity or 1 lvl = recipe.level xp = recipe.baseXP time = baseTime[skillID] if masteryReq ~= nil and masteryReq > 1 then specialReq = Icons._MasteryReq(item.name, masteryReq, false) end -- Some items (such as Arrow shafts) have multiple recipes if type(recipe.alternativeCosts) == 'table' then local reqPart, qtyPart = {}, {} for i, altCost in ipairs(recipe.alternativeCosts) do local reqSubPart = {} for j, 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.GP(recipe.scCost)) end table.insert(reqPart, table.concat(reqSubPart, ', ')) table.insert(qtyPart, Shared.formatnum(baseQty * altCost.quantityMultiplier)) end local sep = "<br/>'''OR''' " req = table.concat(reqPart, sep) qty = table.concat(qtyPart, sep) table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq)) elseif type(recipe.itemCosts) == 'table' and Shared.tableCount(recipe.itemCosts) > 0 then req = recipe.itemCosts table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, baseQty, time, maxTime, specialReq, recipe.gpCost, recipe.scCost)) end end end elseif skillID == SkillEnum.Summoning then -- Summoning local recipe = SkillData.Summoning.Marks[masteryID + 1] if recipe ~= nil and recipe.itemID == item.id then lvl = recipe.level xp = recipe.baseXP qty = recipe.baseQuantity time = 5 -- Create item requirements text local ShardCostArray, OtherCostArray = {}, {} -- Shards for j, cost in ipairs(recipe.itemCosts) do local shard = Items.getItemByID(cost.id) if shard ~= nil then table.insert(ShardCostArray, Icons.Icon({shard.name, type='item', notext=true, qty=cost.qty})) end end -- Other costs local recipeGPCost = SkillData.Summoning.RecipeGPCost 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 #OtherCostArray > 0 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)) end end end -- Woodcutting if item.type == 'Logs' then -- Determine which tree (if any) the log is from for i, tree in ipairs(SkillData.Woodcutting.Trees) do if tree.logID == item.id then skill = 'Woodcutting' lvl = tree.levelRequired time = tree.baseInterval / 1000 xp = tree.baseExperience table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time)) break end end end -- Cooking if item.canEat then for i, recipe in ipairs(SkillData.Cooking.Recipes) do if recipe.itemID == item.id or recipe.perfectCookID == item.id then skill = 'Cooking' lvl = recipe.level xp = recipe.baseXP req = recipe.itemCosts qty = recipe.baseQuantity time = recipe.baseInterval / 1000 table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time)) break end end end -- Farming if item.type == 'Herb' or item.type == 'Logs' or (item.type == 'Food' and item.category ~= 'Cooking') then -- Herb means farming -- Logs/Food might mean farming or might not. Depends on the item for i, seed in ipairs(ItemData.Items) do if seed.grownItemID ~= nil and seed.grownItemID == item.id then skill = 'Farming' lvl = seed.farmingLevel xp = seed.farmingXP time = seed.timeToGrow if item.type == 'Logs' then qty = 35 else qty = 15 end req = {{id = seed.id, qty = (seed.seedsRequired ~= nil and seed.seedsRequired or 1)}} table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time)) break end end end -- Alt. Magic, excludes Gems and Bars -- Bars are handled by getItemSuperheatTable() -- Gems are handled by _getItemLootSourceTable() for i, altSpell in ipairs(MagicData.AltMagic) do if type(altSpell.produces) == 'number' and altSpell.produces == item.id then table.insert(tables, p._buildAltMagicTable(altSpell)) end end if Shared.tableCount(tables) == 0 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 = {} table.insert(resultPart, '{|class="wikitable"\r\n|-') table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type='spell'})) table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements') table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level)) -- The produces property of Alt. Magic spells is as follows: -- -3 = A random gem, using the same weights as Mining -- -2 = A bar of the type being created (Superheat) -- -1 = GP (Alchemy) -- 0 = Undefined -- >0 = Item ID of the item being produced -- The amount produced is determined by the productionRatio property -- The consumes property of Alt. Magic spells is as follows: -- 0 = Any item -- 1 = Junk item -- 2 = Superheat/ores with Coal -- 3 = Superheat/ores without Coal -- 4 = Nothing -- 5 = Coal ore -- Superheat (2, 3) is handled by _getItemSuperheatTable() if spell.consumes ~= nil then local consumeText = { '1 of any item', '1 of any [[Fishing#Junk|Junk]] item', '1 x required ores for the chosen bar', '1 x required ores (except ' .. Icons.Icon({'Coal Ore', type='item'}) .. ') for the chosen bar', nil, Icons.Icon({'Coal Ore', type='item', qty=1}) } local consumeStr = consumeText[spell.consumes + 1] if consumeStr ~= nil then table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials') table.insert(resultPart, '\r\n| ' .. consumeStr) end 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"') if req ~= nil then table.insert(resultPart, '\r\n!colspan="2"|Item Creation') else table.insert(resultPart, '\r\n!colspan="2"|Item Production') end 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.formatnum(Shared.round(time, 2, 0))..'s') if maxTime ~= nil and maxTime > time then table.insert(resultPart, ' - '..Shared.formatnum(Shared.round(maxTime, 2, 0))..'s') 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(MonsterData.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[1] == 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 = {} local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end) if dungeonList ~= nil then for i, dungeon in ipairs(dungeonList) do table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', notext=true})) end end -- Is the item dropped from a cycle of the Impending Darkness event? for i, eventItemID in ipairs(Areas.eventData.rewards) do if item.id == eventItemID then local dungPrefix = (i == Shared.tableCount(Areas.eventData.rewards) and '' or i .. ' ' .. (i == 1 and 'cycle' or 'cycles') .. ' of ') table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({'Impending Darkness Event', type='dungeon', notext=true})) break end end -- Special exceptions for lore books if sourceOverrides['Dungeon'][item.id] ~= nil then table.insert(dungeonStrPart, Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon', notext=true})) end if Shared.tableCount(dungeonStrPart) > 0 then table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ',')) end if Shared.tableCount(killStrPart) > 0 then table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ',')) end --Next: Can we find it in a box? --While we're here, check for upgrades, and growing local lootPart, upgradePart, growPart = {}, {}, {} for i, item2 in pairs(ItemData.Items) do if item2.dropTable ~= nil then for j, loot in ipairs(item2.dropTable) do if loot[1] == item.id then table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true})) break end end end if item2.trimmedItemID ~= nil and item2.trimmedItemID == item.id then table.insert(upgradePart, Icons.Icon({item2.name, type='item', notext=true})) end if item2.grownItemID == item.id then -- Farming table.insert(growPart, Icons.Icon({item2.name, type='item', notext=true})) end end if #lootPart > 0 then table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, ',')) end if #upgradePart > 0 then table.insert(categoryArray, '[[Category:Upgraded Items]]') table.insert(lineArray, 'Upgrading: ' .. table.concat(upgradePart, ',')) end if #growPart > 0 then table.insert(categoryArray, '[[Category:Harvestable Items]]') table.insert(lineArray, 'Growing: ' .. table.concat(growPart, ',')) 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 --AstrologyCheck (Just a brute force for now because only two items) if Shared.contains({'Stardust', 'Golden Stardust'}, item.name) then table.insert(lineArray, Icons.Icon({'Astrology', type='skill'})) end -- Sources discoverable through mastery IDs -- Does _not_ handle: -- Fishing: Junk, special items -- Cooking: perfect items if type(item.masteryID) == 'table' then local skillID, masteryID = item.masteryID[1], item.masteryID[2] local skill = Constants.getSkillName(skillID) local keyData = { [SkillEnum.Fishing] = { ["recipe"] = 'Fish' }, [SkillEnum.Mining] = { ["recipe"] = 'Rocks', ["level"] = 'levelRequired', ["item"] = 'oreID' }, [SkillEnum.Smithing] = {}, [SkillEnum.Fletching] = {}, [SkillEnum.Crafting] = {}, [SkillEnum.Runecrafting] = {}, [SkillEnum.Herblore] = { ["recipe"] = 'Potions', ["item"] = 'potionIDs', ["isItemList"] = true }, [SkillEnum.Summoning] = { ["recipe"] = 'Marks' } } local keys = keyData[skillID] if type(keys) == 'table' then if keys.recipe == nil then keys.recipe = 'Recipes' end if keys.level == nil then keys.level = 'level' end if keys.item == nil then keys.item = 'itemID' end if keys.isItemList == nil then keys.isItemList = false end local recipe = SkillData[skill][keys.recipe][masteryID + 1] if recipe ~= nil and ( (not keys.isItemList and recipe[keys.item] == item.id) or (keys.isItemList and Shared.contains(recipe[keys.item], item.id))) then local levelReq = recipe[keys.level] if levelReq ~= nil then table.insert(lineArray, Icons._SkillReq(skill, levelReq)) end end end end -- Woodcutting for i, tree in ipairs(SkillData.Woodcutting.Trees) do if tree.logID == item.id then table.insert(lineArray, Icons._SkillReq('Woodcutting', tree.levelRequired)) break end end -- Woodcutting: Nests if item.name == 'Bird Nest' then table.insert(lineArray, Icons._SkillReq('Woodcutting', 1)) end -- Fishing: Junk & Specials if Shared.contains(SkillData.Fishing.JunkItems, item.id) then table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]') else for i, specialItem in ipairs(SkillData.Fishing.SpecialItems) do if specialItem[1] == item.id then table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]') break end end end -- Cooking if item.canEat then for i, recipe in ipairs(SkillData.Cooking.Recipes) do if recipe.itemID == item.id or recipe.perfectCookID == item.id then table.insert(lineArray, Icons._SkillReq('Cooking', recipe.level)) break end end end -- Alt. Magic for i, altSpell in ipairs(MagicData.AltMagic) do if type(altSpell.produces) == 'number' and ((altSpell.produces == -3 and item.type == 'Gem' and item.name ~= 'Jadestone') or (altSpell.produces == -2 and item.type == 'Bar') or (altSpell.produces > 0 and altSpell.produces == item.id)) then table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'})) break end end --Finally there are some weird exceptions: --Coal can be acquired via firemaking if item.name == "Coal Ore" then table.insert(lineArray, Icons._SkillReq("Firemaking", 1)) end --Gems can be acquired from Mining if item.type == 'Gem' and item.name ~= 'Jadestone' then table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]') end --Rhaelyx pieces are also special 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.isToken and item.skill ~= nil then table.insert(lineArray, Icons._SkillReq(Constants.getSkillName(item.skill), 1)) 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) local shopSources = Shop.getItemSourceArray(item.id) if Shared.tableCount(shopSources) > 0 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.name == '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.name == 'Signet Ring Half (a)' then table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'})) elseif item.name == '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' chance = string.format(fmt, chance) if weight >= totalWeight then -- Fraction would be 1/1, so only show the percentage chance = 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="' .. chance .. '"| ' .. 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="'.. chance .. '"|'..chance..'%') 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, monster in ipairs(MonsterData.Monsters) do local minqty = 1 local qty = 1 local wt = 0 local totalWt = 0 --Only add bones if this monster has loot (ie appears outside a dungeon) and isn't a boss --... unless we're looking for Shards of course, at which point we'll take any monster with the right bones if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then qty = monster.boneQty ~= nil and monster.boneQty or 1 minqty = qty wt = 1 totalWt = 1 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 totalWt = totalWt + loot[2] if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then wt = loot[2] qty = loot[3] end end end local lootChance = monster.lootChance ~= nil and monster.bones ~= item.id and monster.lootChance or 100 if wt > 0 and lootChance > 0 then -- Item drops when the monster is killed table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100}) end end -- Is the item dropped from any dungeon? local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end) if dungeonList ~= nil then for i, dungeon in ipairs(dungeonList) do table.insert(dropRows, {source = Icons.Icon({dungeon.name, type='dungeon'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1}) end end -- Is the item dropped from a cycle of the Impending Darkness event? for i, eventItemID in ipairs(Areas.eventData.rewards) do if item.id == eventItemID then sourceTxt = Icons.Icon({'Impending Darkness Event', type='dungeon'}) .. (i == Shared.tableCount(Areas.eventData.rewards) and '' or ', Cycle ' .. i) table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1}) break end end --Special exception for the Fire/Infernal Cape and first two lore books as bonus dungeon drops if sourceOverrides['Dungeon'][item.id] ~= nil then local sourceTxt = Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon'}) table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1}) end --Next: Can we find it by rummaging around in another item? for i, item2 in ipairs(ItemData.Items) do if item2.dropTable ~= nil then local qty = 1 local wt = 0 local totalWt = 0 for j, loot in pairs(item2.dropTable) do totalWt = totalWt + loot[2] if loot[1] == item.id then wt = loot[2] if item2.dropQty ~= nil then qty = item2.dropQty[j] end end end if wt > 0 then local sourceTxt = Icons.Icon({item2.name, type='item'}) table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, weight = wt, totalWeight = totalWt}) end end end --Finally, let's try just stealing it local thiefType = Icons.Icon({"Thieving", type='skill'}) 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 = thiefType, minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt}) end --Bonus overtime: Special Fishing table & mining gem table. Also Rags to Riches 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[1] == item.id then fishItem = specialItem end fishTotWeight = fishTotWeight + specialItem[2] end if fishItem ~= nil then local fishSource = '[[Fishing#Special|Special]]' local fishType = Icons.Icon({'Fishing', type='skill'}) table.insert(dropRows, {source = fishSource, type = fishType, minqty = fishItem[3], qty = fishItem[3], weight = fishItem[2], totalWeight = fishTotWeight}) end end --Jadestone is special and doesn't count if item.type == 'Gem' and item.name ~= 'Jadestone' then local mineType = Icons.Icon({'Mining', type='skill'}) local thisGemChance = Items.GemTable[item.name].chance local totalGemChance = 0 for i, gem in pairs(Items.GemTable) do totalGemChance = totalGemChance + gem.chance end table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance}) local magicType = Icons.Icon({'Magic', type = 'skill'}) for i, altSpell in ipairs(MagicData.AltMagic) do if type(altSpell.produces) == 'number' and altSpell.produces == -3 then table.insert(dropRows, {source = Icons.Icon({altSpell.name, type='spell'}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance}) end end end --Make sure to return nothing if there are no drop sources if Shared.tableCount(dropRows) == 0 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 pairs(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 = {} if item.itemsRequired ~= nil then --First, get details on all the required materials local upgradeFrom = {} local materials = {} for i, row in pairs(item.itemsRequired) do local mat = Items.getItemByID(row[1]) --Check to see if the source item can trigger the upgrade if mat.canUpgrade or (mat.type == 'Armour' and mat.canUpgrade == nil) then table.insert(upgradeFrom, Icons.Icon({mat.name, type='item'})) end table.insert(materials, Icons.Icon({mat.name, type='item', qty=row[2]})) end if type(item.trimmedGPCost) == 'number' and item.trimmedGPCost > 0 then table.insert(materials, Icons.GP(item.trimmedGPCost)) end table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]') --[[result = result..'\r\n|-\r\n!style="text-align:right;"|Upgrades From\r\n|' result = result..table.concat(upgradeFrom, '<br/>')--]] table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|') table.insert(resultPart, table.concat(materials, '<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._getItemSuperheatTable(item) --Manually build the Superheat Item table -- Validate that the item can be superheated local canSuperheat, smithRecipe = true, nil if type(item.masteryID) ~= 'table' then canSuperheat = false elseif item.masteryID[1] ~= SkillEnum.Smithing then canSuperheat = false else smithRecipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1] if smithRecipe == nil or smithRecipe.category ~= 0 then canSuperheat = false end end if not canSuperheat 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 thisMat = Items.getItemByID(mat.id) if thisMat.name == 'Coal Ore' then coalString = Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty}) else table.insert(oreStringPart, Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})) 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 spellNames = {'Superheat I', 'Superheat II', 'Superheat III', 'Superheat IV'} for i, sName in pairs(spellNames) do local spell = Magic.getSpell(sName, 'AltMagic') table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type='spell', notext=true, size=50})) table.insert(superheatTable, '||'..Icons.Icon({spell.name, type='spell', 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.consumes == 2 and coalString ~= '' then -- 2 = Superheat with coal, 3 = Superheat without coal table.insert(superheatTable, (Shared.tableCount(oreStringPart) > 0 and ', ' or '') .. coalString) end table.insert(superheatTable, '||style="text-align:center"| ' .. Magic._getSpellRunes(spell)) 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 string.len(shopTable) > 0 then table.insert(resultPart, '===Shop===\r\n'..shopTable) end local creationTable = p._getCreationTable(item) if string.len(creationTable) > 0 then if #resultPart > 0 then table.insert(resultPart, '\r\n') end table.insert(resultPart, '===Creation===\r\n'..creationTable) end local upgradeTable = p._getItemUpgradeTable(item) if string.len(upgradeTable) > 0 then if #resultPart > 0 then table.insert(resultPart, '\r\n') end if string.len(creationTable) == 0 then table.insert(resultPart, '===Creation===\r\n') end table.insert(resultPart, upgradeTable) end if type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Smithing then local recipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1] if recipe ~= nil and recipe.category == 0 then table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt. Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item)) end end local lootTable = p._getItemLootSourceTable(item) if string.len(lootTable) > 0 then if #resultPart > 0 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 Shared.contains(item.validSlots, 'Passive') end) table.sort(itemArray, function(a, b) return a.id < b.id end) for i, item in ipairs(itemArray) do 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, '| '..item.description..'\r\n') end table.insert(resultPart, '|}') return table.concat(resultPart) end function p._getItemMonsterSources(item) local resultArray = {} for i, monster in ipairs(MonsterData.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 chance = 1 weight = 1 if monster.boneQty ~= nil then minQty = monster.boneQty maxQty = monster.boneQty end 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 weight = weight + loot[2] if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then chance = loot[2] maxQty = loot[3] end end local lootChance = monster.lootChance ~= nil 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