12,787
edits
Falterfire (talk | contribs) (Shortened growth time string) |
(_formatLootTable: Re-order columns for table output & include total row) |
||
(37 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
--Some skills have their own modules: | |||
--Module:Magic for Magic | |||
--Module:Prayer for Prayer | |||
--Module:Agility for Agility | |||
--Module:Skills/Gathering for Mining, Fishing, Woodcutting | |||
--Module:Skills/Artisan for Smithing, Cooking, Herblore, etc. | |||
local p = {} | local p = {} | ||
local ItemData = mw.loadData('Module:Items/data') | |||
local SkillData = mw.loadData('Module:Skills/data') | local SkillData = mw.loadData('Module:Skills/data') | ||
local Constants = mw.loadData('Module:Constants/data') | local Constants = mw.loadData('Module:Constants/data') | ||
Line 6: | Line 14: | ||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local ItemSourceTables = require('Module:Items/SourceTables') | |||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
Line 17: | Line 26: | ||
end | end | ||
return nil | return nil | ||
end | |||
function p.getSkillName(skillID) | |||
for skName, ID in Shared.skpairs(Constants.skill) do | |||
if ID == skillID then | |||
return skName | |||
end | |||
end | |||
return nil | |||
end | |||
function p.getThievingNPCByID(ID) | |||
local result = Shared.clone(SkillData.Thieving[ID + 1]) | |||
if result ~= nil then | |||
result.id = ID | |||
end | |||
return result | |||
end | |||
function p.getThievingNPC(name) | |||
local result = nil | |||
for i, npc in pairs(SkillData.Thieving) do | |||
if name == npc.name then | |||
result = Shared.clone(npc) | |||
result.id = i - 1 | |||
break | |||
end | |||
end | |||
return result | |||
end | |||
function p.getThievingNPCStat(frame) | |||
local args = frame.args ~= nil and frame.args or frame | |||
local npcName = args[1] | |||
local statName = args[2] | |||
local npc = p.getThievingNPC(npcName) | |||
if npc == nil then | |||
return 'ERROR: Failed to find Thieving NPC with name ' .. name .. '[[Category:Pages with script errors]]' | |||
end | |||
return p._getThievingNPCStat(npc, statName) | |||
end | |||
function p._getThievingNPCStat(npc, stat) | |||
local itemDropChance = 0.75 | |||
local result = npc[stat] | |||
-- Overrides below | |||
if stat == 'maxHit' then | |||
result = result * 10 | |||
elseif stat == 'lootList' then | |||
return p._formatLootTable(npc['lootTable'], itemDropChance, true) | |||
elseif stat == 'lootTable' then | |||
return p._formatLootTable(npc['lootTable'], itemDropChance, false) | |||
elseif stat == 'requirements' then | |||
if npc['level'] ~= nil then | |||
result = Icons._SkillReq('Thieving', npc['level'], true) | |||
else | |||
result = 'None' | |||
end | |||
elseif (stat == 'lootValue' or stat == 'pickpocketValue') then | |||
if stat == 'pickpocketValue' then | |||
local itemBP = Items.getItem("Bobby's Pocket") | |||
result = (1 + npc['maxCoins']) / 2 + itemBP.sellsFor * (1 / 120) | |||
else | |||
result = 0 | |||
end | |||
result = Shared.round(result + p._getLootTableValue(npc['lootTable']) * itemDropChance, 2, 2) | |||
elseif stat == 'pageName' then | |||
local linkOverrides = { ['Golbin'] = 'Golbin (thieving)' } | |||
result = (linkOverrides[npc['name']] ~= nil and linkOverrides[npc['name']]) or npc['name'] | |||
end | |||
return result | |||
end | |||
function p._getLootTableValue(lootTable) | |||
-- Calculates the average GP value of a given loot table | |||
-- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}} | |||
if Shared.tableCount(lootTable) == 0 then | |||
return 0 | |||
end | |||
local totalWeight = 0 | |||
for i, drop in pairs(lootTable) do | |||
totalWeight = totalWeight + drop[2] | |||
end | |||
if totalWeight == 0 then | |||
return 0 | |||
end | |||
local avgValue = 0 | |||
for i, drop in pairs(lootTable) do | |||
local item = Items.getItemByID(drop[1]) | |||
if item ~= nil then | |||
avgValue = avgValue + item.sellsFor * (drop[2] / totalWeight) | |||
end | |||
end | |||
return avgValue | |||
end | |||
function p._formatLootTable(lootTableIn, chanceMultIn, asList) | |||
-- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}} | |||
if Shared.tableCount(lootTableIn) == 0 then | |||
return '' | |||
end | |||
local chanceMult = (chanceMultIn or 1) * 100 | |||
local lootTable = Shared.clone(lootTableIn) | |||
-- Sort table from most to least common drop | |||
table.sort(lootTable, function(a, b) | |||
if a[2] == b[2] then | |||
return a[1] < b[1] | |||
else | |||
return a[2] > b[2] | |||
end | |||
end) | |||
local totalWeight = 0 | |||
for i, drop in pairs(lootTable) do | |||
totalWeight = totalWeight + drop[2] | |||
end | |||
if totalWeight == 0 then | |||
return '' | |||
end | |||
-- Get the length (in characters) of the largest drop chance so that they can be right aligned | |||
-- [4/16/21]: Adding info for no drop | |||
local maxDropLen = math.max(string.len(Shared.round(100 - chanceMult, 2, 2)), string.len(Shared.round(lootTable[1][2] / totalWeight * chanceMult, 2, 2))) | |||
local returnPart = {} | |||
-- Generate header | |||
if asList then | |||
if chanceMult < 100 then | |||
table.insert(returnPart, '* ' .. string.rep(' ', math.max(0, (maxDropLen - string.len(Shared.round(100 - chanceMult, 2, 2))) * 2)) .. Shared.round(100 - chanceMult, 2, 2) .. '% No Item') | |||
end | |||
else | |||
table.insert(returnPart, '{|class="wikitable sortable"\r\n!Item!!Price!!colspan="2"|Chance') | |||
end | |||
-- Generate row for each item | |||
for i, drop in pairs(lootTable) do | |||
local item, itemText, sellsFor, dropChance = Items.getItemByID(drop[1]), 'Unknown', 0, Shared.round(drop[2] / totalWeight * chanceMult, 2, 2) | |||
if item ~= nil then | |||
itemText, sellsFor = Icons.Icon({item.name, type='item'}), item.sellsFor | |||
end | |||
if asList then | |||
table.insert(returnPart, '* ' .. string.rep(' ', math.max(0, (maxDropLen - string.len(dropChance)) * 2)) .. dropChance .. '% ' .. itemText) | |||
else | |||
table.insert(returnPart, '|-\r\n|' .. itemText) | |||
table.insert(returnPart, '|style="text-align:right;" data-sort-value="' .. sellsFor .. '"|' .. Icons.GP(sellsFor)) | |||
table.insert(returnPart, '|style="text-align:right;" data-sort-value="' .. dropChance .. '"|' .. Shared.fraction(drop[2] * chanceMult, totalWeight * 100)) | |||
table.insert(returnPart, '|style="text-align:right;"|' .. dropChance .. '%') | |||
end | |||
end | |||
if not asList then | |||
table.insert(returnPart, '|-class="sortbottom" \r\n!colspan="2"|Total:') | |||
local textTotChance = '' | |||
if chanceMult < 100 then | |||
textTotChance = '|style="text-align:right"|' .. Shared.fraction(chanceMult, 100) .. '\r\n|' | |||
else | |||
textTotChance = '|colspan="2" ' | |||
end | |||
textTotChance = textTotChance .. 'style="text-align:right;"|' .. Shared.round(chanceMult, 2, 2) .. '%' .. '\r\n|}' | |||
table.insert(returnPart, textTotChance) | |||
end | |||
return table.concat(returnPart, '\r\n') | |||
end | |||
function p.getThievingNPCTable() | |||
local returnPart = {} | |||
-- Create table header | |||
table.insert(returnPart, '{| class="wikitable sortable stickyHeader"') | |||
table.insert(returnPart, '|- class="headerRow-0"\r\n!Target!!Name!!' .. Icons.Icon({'Thieving', type='skill', notext=true}).. ' Level!!Experience!!Max Hit!!Max Coins!!<abbr title="Assumes all loot is sold, and no GP boosts apply (such as those from Mastery & Gloves of Silence)">GP/Theft</abbr>') | |||
-- Create row for each NPC | |||
for i, npc in Shared.skpairs(SkillData.Thieving) do | |||
local linkText = (npc.name ~= p._getThievingNPCStat(npc, 'pageName') and p._getThievingNPCStat(npc, 'pageName') .. '|' .. npc.name) or npc.name | |||
table.insert(returnPart, '|-\r\n|style="text-align: left;" |' .. Icons.Icon({npc.name, type='thieving', size=50, notext=true})) | |||
table.insert(returnPart, '|style="text-align: left;" |[[' .. linkText .. ']]') | |||
table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'level')) | |||
table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'xp')) | |||
table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'maxHit')) | |||
table.insert(returnPart, '|style="text-align: right;" data-sort-value="' .. p._getThievingNPCStat(npc, 'maxCoins') .. '" |' .. Icons.GP(p._getThievingNPCStat(npc, 'maxCoins'))) | |||
table.insert(returnPart, '|style="text-align: right;" data-sort-value="' .. p._getThievingNPCStat(npc, 'pickpocketValue') .. '" |' .. Icons.GP(p._getThievingNPCStat(npc, 'pickpocketValue'))) | |||
end | |||
table.insert(returnPart, '|}') | |||
return table.concat(returnPart, '\r\n') | |||
end | |||
function p.getThievingNavbox() | |||
local returnPart = {} | |||
-- Create table header | |||
table.insert(returnPart, '{| class="wikitable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"') | |||
table.insert(returnPart, '|-\r\n!' .. Icons.Icon({'Thieving', type='skill', notext=true}) .. '[[Thieving|Thieving Targets]]') | |||
table.insert(returnPart, '|-\r\n|') | |||
local npcList = {} | |||
-- Create row for each NPC | |||
for i, npc in Shared.skpairs(SkillData.Thieving) do | |||
local linkText = (npc.name ~= p._getThievingNPCStat(npc, 'pageName') and p._getThievingNPCStat(npc, 'pageName') .. '|' .. npc.name) or npc.name | |||
table.insert(npcList, Icons.Icon({npc.name, type='thieving', notext=true}) .. ' [[' .. linkText .. ']]') | |||
end | |||
table.insert(returnPart, table.concat(npcList, ' • ')) | |||
table.insert(returnPart, '|}') | |||
return table.concat(returnPart, '\r\n') | |||
end | end | ||
Line 75: | Line 294: | ||
local result = '{|class="wikitable sortable stickyHeader"' | local result = '{|class="wikitable sortable stickyHeader"' | ||
result = result..'\r\n|- class=headerRow-0"' | result = result..'\r\n|- class="headerRow-0"' | ||
result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level' | result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level' | ||
result = result..'!!XP!!Growth Time!!Seed Value' | result = result..'!!XP!!Growth Time!!Seed Value' | ||
Line 92: | Line 311: | ||
result = result..'\r\n|-' | result = result..'\r\n|-' | ||
result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]' | result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]' | ||
result = result..'||'..seed.farmingLevel..'||'..seed.farmingXP..'||'..Shared.timeString(seed.timeToGrow, true) | result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP) | ||
result = result..'||'..Icons.GP(seed.sellsFor) | result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true) | ||
result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor) | |||
local crop = Items.getItemByID(seed.grownItemID) | local crop = Items.getItemByID(seed.grownItemID) | ||
result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]' | result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]' | ||
if category == 'Allotment' then | if category == 'Allotment' then | ||
result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..crop.healsFor | result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10) | ||
end | end | ||
result = result..'||'..Icons.GP(crop.sellsFor) | result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor) | ||
result = result..'||'.. | result = result..'||'..ItemSourceTables._getItemSources(seed) | ||
end | end | ||
Line 112: | Line 332: | ||
return p._getFarmingTable(category) | return p._getFarmingTable(category) | ||
end | |||
function p.getFarmingFoodTable(frame) | |||
local result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level' | |||
result = result..'!!Healing!!Value' | |||
local itemArray = Items.getItems(function(item) return item.grownItemID ~= nil end) | |||
table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel end) | |||
for i, item in Shared.skpairs(itemArray) do | |||
local crop = Items.getItemByID(item.grownItemID) | |||
if crop.healsFor ~= nil and crop.healsFor > 0 then | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]' | |||
result = result..'||style="text-align:right;"|'..item.farmingLevel | |||
result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10) | |||
result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor) | |||
end | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | |||
function p.getFarmingPlotTable(frame) | |||
local areaName = frame.args ~= nil and frame.args[1] or frame | |||
local patches = nil | |||
for i, area in Shared.skpairs(SkillData.Farming.Patches) do | |||
if area.areaName == areaName then | |||
patches = area.patches | |||
break | |||
end | |||
end | |||
if patches == nil then | |||
return "ERROR: Invalid area name.[[Category:Pages with script errors" | |||
end | |||
local result = '{|class="wikitable"' | |||
result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost' | |||
for i, patch in Shared.skpairs(patches) do | |||
result = result..'\r\n|-\r\n|'..i | |||
result = result..'||style="text-align:right;" data-sort-value="0"|'..patch.level | |||
if patch.cost == 0 then | |||
result = result..'||Free' | |||
else | |||
result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost) | |||
end | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | |||
function p.getPotionNavbox(frame) | |||
--• | |||
local result = '{| class="wikitable" style="margin:auto; clear:both; width: 100%"' | |||
result = result..'\r\n!colspan=2|'..Icons.Icon({'Herblore', 'Potions', type='skill'}) | |||
local CombatPots = {} | |||
local SkillPots = {} | |||
for i, potData in Shared.skpairs(SkillData.Herblore.ItemData) do | |||
if potData.category == 0 then | |||
table.insert(CombatPots, Icons.Icon({potData.name, type='item', img=(potData.name..' I')})) | |||
else | |||
if potData.name == 'Bird Nests Potion' then | |||
table.insert(SkillPots, Icons.Icon({"Bird Nest Potion", type='item', img="Bird Nest Potion I"})) | |||
else | |||
table.insert(SkillPots, Icons.Icon({potData.name, type='item', img=(potData.name..' I')})) | |||
end | |||
end | |||
end | |||
result = result..'\r\n|-\r\n!Combat Potions\r\n|class="center" style="vertical-align:middle;"' | |||
result = result..'|'..table.concat(CombatPots, ' • ') | |||
result = result..'\r\n|-\r\n!Skill Potions\r\n|class="center" style="vertical-align:middle;"' | |||
result = result..'|'..table.concat(SkillPots, ' • ') | |||
result = result..'\r\n|}' | |||
return result | |||
end | |||
function p.getSmithingTable(frame) | |||
local tableType = frame.args ~= nil and frame.args[1] or frame | |||
local bar = nil | |||
if tableType ~= 'Smelting' then | |||
bar = Items.getItem(tableType) | |||
if bar == nil then | |||
return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with' | |||
elseif bar.type ~= 'Bar' then | |||
return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing" | |||
end | |||
end | |||
local smithList = {} | |||
for i, item in pairs(ItemData.Items) do | |||
if item.smithingLevel ~= nil then | |||
if tableType == 'Smelting' then | |||
if item.type == 'Bar' then | |||
table.insert(smithList, item) | |||
end | |||
else | |||
for j, req in pairs(item.smithReq) do | |||
if req.id == bar.id then | |||
table.insert(smithList, item) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
local result = '{|class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|-class="headerRow-0"' | |||
result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients' | |||
--Adding value/bar for things other than smelting | |||
if bar ~= nil then result = result..'!!Value/Bar' end | |||
table.sort(smithList, function(a, b) | |||
if a.smithingLevel ~= b.smithingLevel then | |||
return a.smithingLevel < b.smithingLevel | |||
else | |||
return a.name < b.name | |||
end end) | |||
for i, item in Shared.skpairs(smithList) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||' | |||
local qty = item.smithingQty ~= nil and item.smithingQty or 1 | |||
if qty > 1 then | |||
result = result..item.smithingQty..'x ' | |||
end | |||
result = result..'[['..item.name..']]' | |||
result = result..'||data-sort-value="'..item.smithingLevel..'"|'..Icons._SkillReq('Smithing', item.smithingLevel) | |||
result = result..'||'..item.smithingXP | |||
local totalValue = item.sellsFor * qty | |||
result = result..'||data-sort-value="'..totalValue..'"|'..Icons.GP(item.sellsFor) | |||
if qty > 1 then | |||
result = result..' (x'..qty..')' | |||
end | |||
result = result..'||' | |||
local barQty = 0 | |||
for i, mat in Shared.skpairs(item.smithReq) do | |||
matItem = Items.getItemByID(mat.id) | |||
if i > 1 then result = result..', ' end | |||
result = result..Icons.Icon({matItem.name, type='item', qty=mat.qty, notext=true}) | |||
if bar ~= nil and mat.id == bar.id then | |||
barQty = mat.qty | |||
end | |||
end | |||
--Add the data for the value per bar | |||
if bar ~= nil then | |||
if barQty == 0 then | |||
result = result..'||data-sort-value="0"|N/A' | |||
else | |||
local barVal = totalValue / barQty | |||
result = result..'||data-sort-value="'..barVal..'"|'..Icons.GP(Shared.round(barVal, 1, 1)) | |||
end | |||
end | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | |||
function p.getFiremakingTable(frame) | |||
local result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|-class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Logs!!'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level' | |||
result = result..'!!XP!!Burn Time!!XP/s!!Bonfire Bonus!!Bonfire Time' | |||
for i, logData in Shared.skpairs(SkillData.Firemaking) do | |||
result = result..'\r\n|-' | |||
local name = Shared.titleCase(logData.type..' Logs') | |||
result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true}) | |||
result = result..'||[['..name..']]' | |||
result = result..'||style ="text-align: right;"|'..logData.level | |||
result = result..'||style ="text-align: right;"|'..logData.xp | |||
local burnTime = logData.interval / 1000 | |||
local XPS = logData.xp / burnTime | |||
result = result..'||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true) | |||
result = result..'||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2) | |||
result = result..'||style ="text-align: right;" data-sort-value="'..logData.bonfireBonus..'"|'..logData.bonfireBonus..'%' | |||
result = result..'||style ="text-align: right;" data-sort-value="'..logData.bonfireInterval..'"|'..Shared.timeString(logData.bonfireInterval / 1000, true) | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
return p | return p |