Module:Skills/Gathering: Difference between revisions
From Melvor Idle
ByteFoolish (talk | contribs) m (Switch to spairs in getMiningGemsTable) |
Falterfire (talk | contribs) (added p.getThievingNPCLootTables) |
||
Line 9: | Line 9: | ||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
local thievingNormalLootChance = 75 | |||
local thievingAreaLootChance = 0.2 | |||
function p.getAxeTable(frame) | function p.getAxeTable(frame) | ||
local toolArray = {} | |||
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do | |||
if Shared.contains(upgrade.name, 'Axe') then | |||
table.insert(toolArray, upgrade) | |||
end | |||
end | |||
local result = '{| class="wikitable"' | |||
result = result..'\r\n!colspan="4"| !!colspan="2"|Cut Time Decrease' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level' | |||
result = result..'!!Cost!!This Axe!!Total' | |||
local total = 0 | |||
for i, tool in Shared.skpairs(toolArray) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true}) | |||
result = result..'||'..tool.name | |||
local level = 1 | |||
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then | |||
--Gonna be lazy and assume there's only the one skill level and it's the one we care about | |||
level = tool.unlockRequirements.skillLevel[1][2] | |||
end | |||
result = result..'||style="text-align:right"|'..level | |||
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp) | |||
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2] | |||
total = total + cutTime | |||
result = result..'||style="text-align:right"|-'..cutTime..'%' | |||
result = result..'||style="text-align:right"|-'..total..'%' | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getPickaxeTable(frame) | function p.getPickaxeTable(frame) | ||
local toolArray = {} | |||
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do | |||
if Shared.contains(upgrade.name, 'Pickaxe') then | |||
table.insert(toolArray, upgrade) | |||
end | |||
end | |||
local result = '{| class="wikitable"' | |||
result = result..'\r\n!colspan="4"| !!colspan="2"|Mine Time Decrease!!colspan="2"|2x Ore Chance' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level' | |||
result = result..'!!Cost!!This Pick!!Total!!This Pick!!Total' | |||
local total = 0 | |||
local total2 = 0 | |||
for i, tool in Shared.skpairs(toolArray) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true}) | |||
result = result..'||'..tool.name | |||
local level = 1 | |||
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then | |||
--Gonna be lazy and assume there's only the one skill level and it's the one we care about | |||
level = tool.unlockRequirements.skillLevel[1][2] | |||
end | |||
result = result..'||style="text-align:right"|'..level | |||
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp) | |||
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2] | |||
total = total + cutTime | |||
result = result..'||style="text-align:right"|-'..cutTime..'%' | |||
result = result..'||style="text-align:right"|-'..total..'%' | |||
local OreDouble = tool.contains.modifiers.increasedChanceToDoubleOres | |||
total2 = total2 + OreDouble | |||
result = result..'||style="text-align:right"|+'..OreDouble..'%' | |||
result = result..'||style="text-align:right"|+'..total2..'%' | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getRodTable(frame) | function p.getRodTable(frame) | ||
local toolArray = {} | |||
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do | |||
if Shared.contains(upgrade.name, 'Fishing Rod') then | |||
table.insert(toolArray, upgrade) | |||
end | |||
end | |||
local result = '{| class="wikitable"' | |||
result = result..'\r\n!colspan="4"| !!colspan="2"|Catch Time Decrease' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level' | |||
result = result..'!!Cost!!This Rod!!Total' | |||
local total = 0 | |||
for i, tool in Shared.skpairs(toolArray) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true}) | |||
result = result..'||'..tool.name | |||
local level = 1 | |||
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then | |||
--Gonna be lazy and assume there's only the one skill level and it's the one we care about | |||
level = tool.unlockRequirements.skillLevel[1][2] | |||
end | |||
result = result..'||style="text-align:right"|'..level | |||
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp) | |||
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2] | |||
total = total + cutTime | |||
result = result..'||style="text-align:right"|-'..cutTime..'%' | |||
result = result..'||style="text-align:right"|-'..total..'%' | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getTreesTable(frame) | function p.getTreesTable(frame) | ||
local result = '{| class="wikitable sortable"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Tree!!colspan="2"|Logs!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level' | |||
result = result..'!!XP!!Cut Time!!XP/s!!GP/s' | |||
for i, tree in Shared.skpairs(SkillData.Woodcutting.Trees) do | |||
result = result..'\r\n|-' | |||
local treeName = Shared.titleCase(tree.type..' tree') | |||
local logName = Shared.titleCase(tree.type..' logs') | |||
result = result..'\r\n|style="min-width:25px" data-sort-value="'..treeName..'"|'..Icons.Icon({logName, img=treeName, type='tree', notext=true, size=50}) | |||
result = result..'||'..treeName..'' | |||
result = result..'||style="min-width:25px" data-sort-value="'..logName..'"|'..Icons.Icon({logName, type='item', notext=true, size=50}) | |||
result = result..'||[['..logName..']]' | |||
result = result..'||style="text-align:right"|'..tree.level | |||
result = result..'||style="text-align:right"|'..tree.xp | |||
result = result..'||style="text-align:right" data-sort-value="'..tree.interval..'"|'..Shared.timeString(tree.interval/1000, true) | |||
local XPs = tree.xp / (tree.interval / 1000) | |||
local Log = Items.getItemByID(i - 1) | |||
local GPs = Log.sellsFor / (tree.interval / 1000) | |||
result = result..'||style="text-align:right"|'..Shared.round(XPs, 2, 2) | |||
result = result..'||style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2)) | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getSpecialFishingTable(frame) | function p.getSpecialFishingTable(frame) | ||
local lootValue = 0 | |||
local totalWt = Items.specialFishWt | |||
local result = '' | |||
result = result..'\r\n{|class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!Item' | |||
result = result..'!!Price!!colspan="2"|Chance' | |||
--Sort the loot table by weight in descending order | |||
table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end) | |||
for i, row in pairs(Items.specialFishLoot) do | |||
local thisItem = Items.getItemByID(row[1]) | |||
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}) | |||
result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"' | |||
result = result..'|'..Icons.GP(thisItem.sellsFor) | |||
local dropChance = (row[2] / totalWt) * 100 | |||
result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"' | |||
result = result..'|'..Shared.fraction(row[2], totalWt) | |||
result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%' | |||
lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor) | |||
end | |||
result = result..'\r\n|}' | |||
result = result..'\r\nThe average value of a roll on the special fishing loot table is '..Icons.GP(Shared.round(lootValue, 2, 0)) | |||
return result | |||
end | end | ||
function p.getFishingJunkTable(frame) | function p.getFishingJunkTable(frame) | ||
local result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Item!!Value' | |||
local itemArray = Items.getItems(function(item) return item.type == "Junk" end) | |||
table.sort(itemArray, function(a, b) return a.name < b.name end) | |||
for i, item in Shared.skpairs(itemArray) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext='true', size='50'})..'||[['..item.name..']]' | |||
result = result..'||style="text-align:right;" data-sort-value="'..item.sellsFor..'"|'..Icons.GP(item.sellsFor) | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getMiningOresTable(frame) | function p.getMiningOresTable(frame) | ||
local result = '{|class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan=2|Ore!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level' | |||
result = result..'!!XP!!Respawn Time!!Ore Value' | |||
local mineData = Shared.clone(SkillData.Mining.Rocks) | |||
table.sort(mineData, function(a, b) return a.level < b.level end) | |||
for i, oreData in Shared.skpairs(mineData) do | |||
local ore = Items.getItemByID(oreData.ore) | |||
result = result..'\r\n|-\r\n|style="min-width:25px"|'..Icons.Icon({ore.name, type='item', size='50', notext=true})..'||[['..ore.name..']]' | |||
result = result..'||style="text-align:right"|'..oreData.level..'||style="text-align:right"|'..ore.miningXP | |||
result = result..'||style="text-align:right" data-sort-value="'..oreData.respawnInterval..'"|' | |||
result = result..Shared.timeString(oreData.respawnInterval / 1000, true) | |||
result = result..'||data-sort-value="'..ore.sellsFor..'"|'..Icons.GP(ore.sellsFor) | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getMiningGemsTable(frame) | function p.getMiningGemsTable(frame) | ||
local result = '{|class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan=2|Gem!!Gem Chance!!Gem Price' | |||
-- Sort gems by ID order | |||
for i, gemData in Shared.spairs(Items.GemTable, function(t,a,b) return t[a].id < t[b].id end) do | |||
local gem = Items.getItemByID(gemData.id) | |||
result = result..'\r\n|-\r\n|style="min-width:25px"|' | |||
result = result..Icons.Icon({gem.name, type='item', size='50', notext=true})..'||[['..gem.name..']]' | |||
result = result..'||style="text-align:right"|'..string.format("%.1f%%", gemData.chance) | |||
result = result..'||data-sort-value="'..gem.sellsFor..'"|'..Icons.GP(gem.sellsFor) | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getFishTable(frame) | function p.getFishTable(frame) | ||
local data = Items.getItems(function(item) return item.fishingID ~= nil end) | |||
table.sort(data, function(a, b) return a.fishingID < b.fishingID end) | |||
local result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!Fish\r\n!Name\r\n!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level\r\n!Catch Time' | |||
result = result..'\r\n!Experience\r\n!Fish Price\r\n!'..Icons.Icon({'Cooking', type='skill', notext=true})..' Level' | |||
for i, fish in Shared.skpairs(data) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', size='50', notext=true}) | |||
result = result..'\r\n| style ="text-align: left;" |[['..fish.name..']]' | |||
result = result..'\r\n| style="text-align:right"|'..fish.fishingLevel | |||
local timeSortVal = (fish.minFishingInterval + fish.maxFishingInterval) / 2 | |||
local timeStr = string.format("%.1fs-%.1fs", (fish.minFishingInterval/1000), (fish.maxFishingInterval/1000)) | |||
result = result..'\r\n| style="text-align:right" data-sort-value="'..timeSortVal..'"|'..timeStr | |||
result = result..'\r\n| style="text-align:right"|'..fish.fishingXP | |||
result = result..'\r\n| style="text-align:right"|'..fish.sellsFor | |||
local cookStr = "N/A" | |||
if fish.cookingLevel ~= nil then | |||
cookStr = fish.cookingLevel | |||
end | |||
result = result..'\r\n| style="text-align:right"|'..cookStr | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getFishingAreasTable(frame) | function p.getFishingAreasTable(frame) | ||
local result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!Name\r\n!Fish\r\n!Fish Chance' | |||
result = result..'\r\n!Junk Chance\r\n!Special Chance' | |||
for i, area in Shared.skpairs(SkillData.Fishing.Areas) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n| style ="text-align: left;" |'..area.name | |||
local fishArray = {} | |||
for j, fish in Shared.skpairs(area.fish) do | |||
local fishTable = Items.getItems(function(item) return item.fishingID == fish end) | |||
local fishItem = fishTable[0] or fishTable[1] | |||
table.insert(fishArray, Icons.Icon({fishItem.name, type='item'})) | |||
end | |||
result = result..'\r\n|'..table.concat(fishArray, '<br />') | |||
result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%' | |||
result = result..'\r\n| style="text-align:right"|'..area.junkChance..'%' | |||
result = result..'\r\n| style="text-align:right"|'..area.specialChance..'%' | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | |||
function p.getThievingNPC(npcName) | |||
local result = nil | |||
for i, npc in Shared.skpairs(SkillData.Thieving.NPCs) do | |||
if npc.name == npcName then | |||
result = Shared.clone(npc) | |||
break | |||
end | |||
end | |||
return result | |||
end | |||
function p.getThievingNPCArea(npc) | |||
if type(npc) == 'string' then | |||
npc = p.getThievingNPC(npc) | |||
end | |||
local result = nil | |||
for i, area in Shared.skpairs(SkillData.Thieving.Areas) do | |||
for j, npcID in pairs(area.npcs) do | |||
if npcID == npc.id then | |||
result = area | |||
break | |||
end | |||
end | |||
end | |||
return result | |||
end | |||
function p._getThievingNPCLootTables(npc) | |||
local result = '' | |||
local sectionTxt = {} | |||
--Five sections here: GP, normal loot, area loot, rare loot, and unique item | |||
--First up, GP: | |||
local gpTxt = 'Successfully pickpocketing '..npc.name..' will always give '..Icons.GP(1, npc.maxGP) | |||
table.insert(sectionTxt, gpTxt) | |||
--Next up, normal loot: | |||
--(Skip if no loot) | |||
if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then | |||
local normalTxt = '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:' | |||
local totalWt = 0 | |||
local lootChance = thievingNormalLootChance | |||
local lootValue = 0 | |||
--First loop through to get the total weight so we have it for later | |||
for i, loot in pairs(npc.lootTable) do | |||
totalWt = totalWt + loot[2] | |||
end | |||
normalTxt = normalTxt..'\r\n{|class="wikitable sortable"' | |||
normalTxt = normalTxt..'\r\n!Item!!Qty' | |||
normalTxt = normalTxt..'!!Price!!colspan="2"|Chance' | |||
--Then sort the loot table by weight | |||
table.sort(npc.lootTable, function(a, b) return a[2] > b[2] end) | |||
for i, row in Shared.skpairs(npc.lootTable) do | |||
local thisItem = Items.getItemByID(row[1]) | |||
local maxQty = row[3] | |||
if thisItem ~= nil then | |||
normalTxt = normalTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}) | |||
else | |||
normalTxt = normalTxt..'\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]' | |||
end | |||
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..maxQty..'"|' | |||
if maxQty > 1 then | |||
normalTxt = normalTxt.. '1 - ' | |||
end | |||
normalTxt = normalTxt..Shared.formatnum(row[3]) | |||
--Adding price columns | |||
local itemPrice = 0 | |||
if thisItem == nil then | |||
normalTxt = normalTxt..'||data-sort-value="0"|???' | |||
else | |||
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0 | |||
if itemPrice == 0 or maxQty == 1 then | |||
normalTxt = normalTxt..'||'..Icons.GP(itemPrice) | |||
else | |||
normalTxt = normalTxt..'||'..Icons.GP(itemPrice, itemPrice * maxQty) | |||
end | |||
end | |||
--Getting the drop chance | |||
local dropChance = (row[2] / totalWt * lootChance) | |||
if dropChance ~= 100 then | |||
--Show fraction as long as it isn't going to be 1/1 | |||
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..row[2]..'"' | |||
normalTxt = normalTxt..'|'..Shared.fraction(row[2] * lootChance, totalWt * 100) | |||
normalTxt = normalTxt..'||' | |||
else | |||
normalTxt = normalTxt..'||colspan="2" data-sort-value="'..row[2]..'"' | |||
end | |||
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%' | |||
--Adding to the average loot value based on price & dropchance | |||
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2)) | |||
end | |||
if multiDrop then | |||
normalTxt = normalTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:' | |||
if lootChance < 100 then | |||
normalTxt = normalTxt..'\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||' | |||
else | |||
normalTxt = normalTxt..'\r\n|colspan="2" ' | |||
end | |||
normalTxt = normalTxt..'style="text-align:right"|'..lootChance..'.00%' | |||
end | |||
normalTxt = normalTxt..'\r\n|}' | |||
table.insert(sectionTxt, normalTxt) | |||
end | |||
--After normal drops, add in rare drops | |||
local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket' | |||
rareTxt = rareTxt..'\r\n{|class="wikitable sortable"' | |||
rareTxt = rareTxt..'\r\n!Item!!Qty' | |||
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance' | |||
for i, drop in pairs(SkillData.Thieving.RareItems) do | |||
local thisItem = Items.getItemByID(drop.itemID) | |||
local odds = drop.chance | |||
local roundOdds = math.floor(odds * 10000) | |||
rareTxt = rareTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}) | |||
rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor) | |||
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(roundOdds, 1000000) | |||
rareTxt = rareTxt..'||'..Shared.round(odds, 2, 2)..'%' | |||
end | |||
rareTxt = rareTxt..'\r\n|}' | |||
table.insert(sectionTxt, rareTxt) | |||
local areaTxt = '===Possible Area Unique Drops===' | |||
areaTxt = areaTxt..'\r\nAny Area Unique Drop is equally likely to be obtained after a successful pickpocket. ' | |||
areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.' | |||
local area = p.getThievingNPCArea(npc) | |||
areaTxt = areaTxt..'\r\n{|class="wikitable sortable"' | |||
areaTxt = areaTxt..'\r\n!Item!!Qty' | |||
areaTxt = areaTxt..'!!Price!!colspan="2"|Chance' | |||
local dropCount = Shared.tableCount(area.uniqueDrops) | |||
local areaDropChance = thievingAreaLootChance / dropCount | |||
local dropLines = {} | |||
for i, drop in pairs(area.uniqueDrops) do | |||
local thisItem = Items.getItemByID(drop.itemID) | |||
local lineTxt = '' | |||
lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}) | |||
lineTxt = lineTxt..'||'..drop.qty..'||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor) | |||
lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(areaDropChance, 100) | |||
lineTxt = lineTxt..'||'..Shared.round(areaDropChance, 2, 2)..'%' | |||
dropLines[thisItem.name] = lineTxt | |||
end | |||
for i, txt in Shared.skpairs(dropLines) do | |||
areaTxt = areaTxt..txt | |||
end | |||
areaTxt = areaTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:' | |||
areaTxt = areaTxt..'\r\n|style="text-align:right"|'..Shared.fraction(thievingAreaLootChance, 100)..'||' | |||
areaTxt = areaTxt..'style="text-align:right"|'..Shared.round(thievingAreaLootChance, 2, 2)..'%' | |||
areaTxt = areaTxt..'\r\n|}' | |||
table.insert(sectionTxt, areaTxt) | |||
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then | |||
local uniqueTxt = '===Possible NPC Unique Drop===' | |||
uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.' | |||
uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.' | |||
local thisItem = Items.getItemByID(npc.uniqueDrop.itemID) | |||
uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is ' | |||
if npc.uniqueDrop.qty > 1 then | |||
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.qty}) | |||
else | |||
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'}) | |||
end | |||
table.insert(sectionTxt, uniqueTxt) | |||
end | |||
return table.concat(sectionTxt, '\r\n') | |||
end | |||
function p.getThievingNPCLootTables(frame) | |||
local npcName = frame.args ~= nil and frame.args[1] or frame | |||
local npc = p.getThievingNPC(npcName) | |||
if npc == nil then | |||
return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]" | |||
end | |||
return p._getThievingNPCLootTables(npc) | |||
end | end | ||
return p | return p |
Revision as of 20:19, 30 September 2021
Documentation for this module may be created at Module:Skills/Gathering/doc
--Splitting some functions into here to avoid bloating a single file
local p = {}
local SkillData = mw.loadData('Module:Skills/data')
local ShopData = mw.loadData('Module:Shop/data')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local thievingNormalLootChance = 75
local thievingAreaLootChance = 0.2
function p.getAxeTable(frame)
local toolArray = {}
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
if Shared.contains(upgrade.name, 'Axe') then
table.insert(toolArray, upgrade)
end
end
local result = '{| class="wikitable"'
result = result..'\r\n!colspan="4"| !!colspan="2"|Cut Time Decrease'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level'
result = result..'!!Cost!!This Axe!!Total'
local total = 0
for i, tool in Shared.skpairs(toolArray) do
result = result..'\r\n|-'
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
result = result..'||'..tool.name
local level = 1
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
--Gonna be lazy and assume there's only the one skill level and it's the one we care about
level = tool.unlockRequirements.skillLevel[1][2]
end
result = result..'||style="text-align:right"|'..level
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
total = total + cutTime
result = result..'||style="text-align:right"|-'..cutTime..'%'
result = result..'||style="text-align:right"|-'..total..'%'
end
result = result..'\r\n|}'
return result
end
function p.getPickaxeTable(frame)
local toolArray = {}
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
if Shared.contains(upgrade.name, 'Pickaxe') then
table.insert(toolArray, upgrade)
end
end
local result = '{| class="wikitable"'
result = result..'\r\n!colspan="4"| !!colspan="2"|Mine Time Decrease!!colspan="2"|2x Ore Chance'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
result = result..'!!Cost!!This Pick!!Total!!This Pick!!Total'
local total = 0
local total2 = 0
for i, tool in Shared.skpairs(toolArray) do
result = result..'\r\n|-'
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
result = result..'||'..tool.name
local level = 1
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
--Gonna be lazy and assume there's only the one skill level and it's the one we care about
level = tool.unlockRequirements.skillLevel[1][2]
end
result = result..'||style="text-align:right"|'..level
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
total = total + cutTime
result = result..'||style="text-align:right"|-'..cutTime..'%'
result = result..'||style="text-align:right"|-'..total..'%'
local OreDouble = tool.contains.modifiers.increasedChanceToDoubleOres
total2 = total2 + OreDouble
result = result..'||style="text-align:right"|+'..OreDouble..'%'
result = result..'||style="text-align:right"|+'..total2..'%'
end
result = result..'\r\n|}'
return result
end
function p.getRodTable(frame)
local toolArray = {}
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
if Shared.contains(upgrade.name, 'Fishing Rod') then
table.insert(toolArray, upgrade)
end
end
local result = '{| class="wikitable"'
result = result..'\r\n!colspan="4"| !!colspan="2"|Catch Time Decrease'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level'
result = result..'!!Cost!!This Rod!!Total'
local total = 0
for i, tool in Shared.skpairs(toolArray) do
result = result..'\r\n|-'
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
result = result..'||'..tool.name
local level = 1
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
--Gonna be lazy and assume there's only the one skill level and it's the one we care about
level = tool.unlockRequirements.skillLevel[1][2]
end
result = result..'||style="text-align:right"|'..level
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
total = total + cutTime
result = result..'||style="text-align:right"|-'..cutTime..'%'
result = result..'||style="text-align:right"|-'..total..'%'
end
result = result..'\r\n|}'
return result
end
function p.getTreesTable(frame)
local result = '{| class="wikitable sortable"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Tree!!colspan="2"|Logs!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level'
result = result..'!!XP!!Cut Time!!XP/s!!GP/s'
for i, tree in Shared.skpairs(SkillData.Woodcutting.Trees) do
result = result..'\r\n|-'
local treeName = Shared.titleCase(tree.type..' tree')
local logName = Shared.titleCase(tree.type..' logs')
result = result..'\r\n|style="min-width:25px" data-sort-value="'..treeName..'"|'..Icons.Icon({logName, img=treeName, type='tree', notext=true, size=50})
result = result..'||'..treeName..''
result = result..'||style="min-width:25px" data-sort-value="'..logName..'"|'..Icons.Icon({logName, type='item', notext=true, size=50})
result = result..'||[['..logName..']]'
result = result..'||style="text-align:right"|'..tree.level
result = result..'||style="text-align:right"|'..tree.xp
result = result..'||style="text-align:right" data-sort-value="'..tree.interval..'"|'..Shared.timeString(tree.interval/1000, true)
local XPs = tree.xp / (tree.interval / 1000)
local Log = Items.getItemByID(i - 1)
local GPs = Log.sellsFor / (tree.interval / 1000)
result = result..'||style="text-align:right"|'..Shared.round(XPs, 2, 2)
result = result..'||style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2))
end
result = result..'\r\n|}'
return result
end
function p.getSpecialFishingTable(frame)
local lootValue = 0
local totalWt = Items.specialFishWt
local result = ''
result = result..'\r\n{|class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!Item'
result = result..'!!Price!!colspan="2"|Chance'
--Sort the loot table by weight in descending order
table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end)
for i, row in pairs(Items.specialFishLoot) do
local thisItem = Items.getItemByID(row[1])
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
result = result..'|'..Icons.GP(thisItem.sellsFor)
local dropChance = (row[2] / totalWt) * 100
result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
result = result..'|'..Shared.fraction(row[2], totalWt)
result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor)
end
result = result..'\r\n|}'
result = result..'\r\nThe average value of a roll on the special fishing loot table is '..Icons.GP(Shared.round(lootValue, 2, 0))
return result
end
function p.getFishingJunkTable(frame)
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Item!!Value'
local itemArray = Items.getItems(function(item) return item.type == "Junk" end)
table.sort(itemArray, function(a, b) return a.name < b.name end)
for i, item in Shared.skpairs(itemArray) do
result = result..'\r\n|-'
result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext='true', size='50'})..'||[['..item.name..']]'
result = result..'||style="text-align:right;" data-sort-value="'..item.sellsFor..'"|'..Icons.GP(item.sellsFor)
end
result = result..'\r\n|}'
return result
end
function p.getMiningOresTable(frame)
local result = '{|class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan=2|Ore!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
result = result..'!!XP!!Respawn Time!!Ore Value'
local mineData = Shared.clone(SkillData.Mining.Rocks)
table.sort(mineData, function(a, b) return a.level < b.level end)
for i, oreData in Shared.skpairs(mineData) do
local ore = Items.getItemByID(oreData.ore)
result = result..'\r\n|-\r\n|style="min-width:25px"|'..Icons.Icon({ore.name, type='item', size='50', notext=true})..'||[['..ore.name..']]'
result = result..'||style="text-align:right"|'..oreData.level..'||style="text-align:right"|'..ore.miningXP
result = result..'||style="text-align:right" data-sort-value="'..oreData.respawnInterval..'"|'
result = result..Shared.timeString(oreData.respawnInterval / 1000, true)
result = result..'||data-sort-value="'..ore.sellsFor..'"|'..Icons.GP(ore.sellsFor)
end
result = result..'\r\n|}'
return result
end
function p.getMiningGemsTable(frame)
local result = '{|class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan=2|Gem!!Gem Chance!!Gem Price'
-- Sort gems by ID order
for i, gemData in Shared.spairs(Items.GemTable, function(t,a,b) return t[a].id < t[b].id end) do
local gem = Items.getItemByID(gemData.id)
result = result..'\r\n|-\r\n|style="min-width:25px"|'
result = result..Icons.Icon({gem.name, type='item', size='50', notext=true})..'||[['..gem.name..']]'
result = result..'||style="text-align:right"|'..string.format("%.1f%%", gemData.chance)
result = result..'||data-sort-value="'..gem.sellsFor..'"|'..Icons.GP(gem.sellsFor)
end
result = result..'\r\n|}'
return result
end
function p.getFishTable(frame)
local data = Items.getItems(function(item) return item.fishingID ~= nil end)
table.sort(data, function(a, b) return a.fishingID < b.fishingID end)
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!Fish\r\n!Name\r\n!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level\r\n!Catch Time'
result = result..'\r\n!Experience\r\n!Fish Price\r\n!'..Icons.Icon({'Cooking', type='skill', notext=true})..' Level'
for i, fish in Shared.skpairs(data) do
result = result..'\r\n|-'
result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', size='50', notext=true})
result = result..'\r\n| style ="text-align: left;" |[['..fish.name..']]'
result = result..'\r\n| style="text-align:right"|'..fish.fishingLevel
local timeSortVal = (fish.minFishingInterval + fish.maxFishingInterval) / 2
local timeStr = string.format("%.1fs-%.1fs", (fish.minFishingInterval/1000), (fish.maxFishingInterval/1000))
result = result..'\r\n| style="text-align:right" data-sort-value="'..timeSortVal..'"|'..timeStr
result = result..'\r\n| style="text-align:right"|'..fish.fishingXP
result = result..'\r\n| style="text-align:right"|'..fish.sellsFor
local cookStr = "N/A"
if fish.cookingLevel ~= nil then
cookStr = fish.cookingLevel
end
result = result..'\r\n| style="text-align:right"|'..cookStr
end
result = result..'\r\n|}'
return result
end
function p.getFishingAreasTable(frame)
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!Name\r\n!Fish\r\n!Fish Chance'
result = result..'\r\n!Junk Chance\r\n!Special Chance'
for i, area in Shared.skpairs(SkillData.Fishing.Areas) do
result = result..'\r\n|-'
result = result..'\r\n| style ="text-align: left;" |'..area.name
local fishArray = {}
for j, fish in Shared.skpairs(area.fish) do
local fishTable = Items.getItems(function(item) return item.fishingID == fish end)
local fishItem = fishTable[0] or fishTable[1]
table.insert(fishArray, Icons.Icon({fishItem.name, type='item'}))
end
result = result..'\r\n|'..table.concat(fishArray, '<br />')
result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%'
result = result..'\r\n| style="text-align:right"|'..area.junkChance..'%'
result = result..'\r\n| style="text-align:right"|'..area.specialChance..'%'
end
result = result..'\r\n|}'
return result
end
function p.getThievingNPC(npcName)
local result = nil
for i, npc in Shared.skpairs(SkillData.Thieving.NPCs) do
if npc.name == npcName then
result = Shared.clone(npc)
break
end
end
return result
end
function p.getThievingNPCArea(npc)
if type(npc) == 'string' then
npc = p.getThievingNPC(npc)
end
local result = nil
for i, area in Shared.skpairs(SkillData.Thieving.Areas) do
for j, npcID in pairs(area.npcs) do
if npcID == npc.id then
result = area
break
end
end
end
return result
end
function p._getThievingNPCLootTables(npc)
local result = ''
local sectionTxt = {}
--Five sections here: GP, normal loot, area loot, rare loot, and unique item
--First up, GP:
local gpTxt = 'Successfully pickpocketing '..npc.name..' will always give '..Icons.GP(1, npc.maxGP)
table.insert(sectionTxt, gpTxt)
--Next up, normal loot:
--(Skip if no loot)
if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then
local normalTxt = '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:'
local totalWt = 0
local lootChance = thievingNormalLootChance
local lootValue = 0
--First loop through to get the total weight so we have it for later
for i, loot in pairs(npc.lootTable) do
totalWt = totalWt + loot[2]
end
normalTxt = normalTxt..'\r\n{|class="wikitable sortable"'
normalTxt = normalTxt..'\r\n!Item!!Qty'
normalTxt = normalTxt..'!!Price!!colspan="2"|Chance'
--Then sort the loot table by weight
table.sort(npc.lootTable, function(a, b) return a[2] > b[2] end)
for i, row in Shared.skpairs(npc.lootTable) do
local thisItem = Items.getItemByID(row[1])
local maxQty = row[3]
if thisItem ~= nil then
normalTxt = normalTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
else
normalTxt = normalTxt..'\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]'
end
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..maxQty..'"|'
if maxQty > 1 then
normalTxt = normalTxt.. '1 - '
end
normalTxt = normalTxt..Shared.formatnum(row[3])
--Adding price columns
local itemPrice = 0
if thisItem == nil then
normalTxt = normalTxt..'||data-sort-value="0"|???'
else
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
if itemPrice == 0 or maxQty == 1 then
normalTxt = normalTxt..'||'..Icons.GP(itemPrice)
else
normalTxt = normalTxt..'||'..Icons.GP(itemPrice, itemPrice * maxQty)
end
end
--Getting the drop chance
local dropChance = (row[2] / totalWt * lootChance)
if dropChance ~= 100 then
--Show fraction as long as it isn't going to be 1/1
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..row[2]..'"'
normalTxt = normalTxt..'|'..Shared.fraction(row[2] * lootChance, totalWt * 100)
normalTxt = normalTxt..'||'
else
normalTxt = normalTxt..'||colspan="2" data-sort-value="'..row[2]..'"'
end
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
--Adding to the average loot value based on price & dropchance
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
end
if multiDrop then
normalTxt = normalTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
if lootChance < 100 then
normalTxt = normalTxt..'\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||'
else
normalTxt = normalTxt..'\r\n|colspan="2" '
end
normalTxt = normalTxt..'style="text-align:right"|'..lootChance..'.00%'
end
normalTxt = normalTxt..'\r\n|}'
table.insert(sectionTxt, normalTxt)
end
--After normal drops, add in rare drops
local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket'
rareTxt = rareTxt..'\r\n{|class="wikitable sortable"'
rareTxt = rareTxt..'\r\n!Item!!Qty'
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance'
for i, drop in pairs(SkillData.Thieving.RareItems) do
local thisItem = Items.getItemByID(drop.itemID)
local odds = drop.chance
local roundOdds = math.floor(odds * 10000)
rareTxt = rareTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(roundOdds, 1000000)
rareTxt = rareTxt..'||'..Shared.round(odds, 2, 2)..'%'
end
rareTxt = rareTxt..'\r\n|}'
table.insert(sectionTxt, rareTxt)
local areaTxt = '===Possible Area Unique Drops==='
areaTxt = areaTxt..'\r\nAny Area Unique Drop is equally likely to be obtained after a successful pickpocket. '
areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.'
local area = p.getThievingNPCArea(npc)
areaTxt = areaTxt..'\r\n{|class="wikitable sortable"'
areaTxt = areaTxt..'\r\n!Item!!Qty'
areaTxt = areaTxt..'!!Price!!colspan="2"|Chance'
local dropCount = Shared.tableCount(area.uniqueDrops)
local areaDropChance = thievingAreaLootChance / dropCount
local dropLines = {}
for i, drop in pairs(area.uniqueDrops) do
local thisItem = Items.getItemByID(drop.itemID)
local lineTxt = ''
lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
lineTxt = lineTxt..'||'..drop.qty..'||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(areaDropChance, 100)
lineTxt = lineTxt..'||'..Shared.round(areaDropChance, 2, 2)..'%'
dropLines[thisItem.name] = lineTxt
end
for i, txt in Shared.skpairs(dropLines) do
areaTxt = areaTxt..txt
end
areaTxt = areaTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
areaTxt = areaTxt..'\r\n|style="text-align:right"|'..Shared.fraction(thievingAreaLootChance, 100)..'||'
areaTxt = areaTxt..'style="text-align:right"|'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
areaTxt = areaTxt..'\r\n|}'
table.insert(sectionTxt, areaTxt)
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
local uniqueTxt = '===Possible NPC Unique Drop==='
uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.'
uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.'
local thisItem = Items.getItemByID(npc.uniqueDrop.itemID)
uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is '
if npc.uniqueDrop.qty > 1 then
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.qty})
else
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'})
end
table.insert(sectionTxt, uniqueTxt)
end
return table.concat(sectionTxt, '\r\n')
end
function p.getThievingNPCLootTables(frame)
local npcName = frame.args ~= nil and frame.args[1] or frame
local npc = p.getThievingNPC(npcName)
if npc == nil then
return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
end
return p._getThievingNPCLootTables(npc)
end
return p