Anonymous

Module:Skills/Gathering: Difference between revisions

From Melvor Idle
Move functions to Module:Skills; Move Farming functions from Module:Skills
(_buildAstrologyModifierArray: Further fixes to key/value pair return value)
(Move functions to Module:Skills; Move Farming functions from Module:Skills)
(9 intermediate revisions by 2 users not shown)
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 Skills = require('Module:Skills')
local thievingNormalLootChance = 75
local ItemSourceTables = require('Module:Items/SourceTables')
local thievingAreaLootChance = 0.2
 
function p.getConstellationByID(constID)
return SkillData.Astrology.Constellations[constID]
end
 
function p.getConstellation(constName)
for i, const in ipairs(SkillData.Astrology.Constellations) do
if const.name == constName then
return const
end
end
return nil
end
 
function p.getConstellations(checkFunc)
local result = {}
for i, const in ipairs(SkillData.Astrology.Constellations) do
if checkFunc(const) then
table.insert(result, const)
end
end
return result
end


function p.getAxeTable(frame)
function p.getAxeTable(frame)
Line 172: Line 148:
result = result..'||style="min-width:25px" data-sort-value="'..logName..'"|'..Icons.Icon({logName, type='item', notext=true, size=50})
result = result..'||style="min-width:25px" data-sort-value="'..logName..'"|'..Icons.Icon({logName, type='item', notext=true, size=50})
result = result..'||'..Icons.Icon({logName, type='item', noicon=true})
result = result..'||'..Icons.Icon({logName, type='item', noicon=true})
result = result..'||style="text-align:right"|'..tree.level
result = result..'||style="text-align:right"|'..tree.levelRequired
result = result..'||style="text-align:right"|'..tree.xp
result = result..'||style="text-align:right"|'..tree.baseExperience
result = result..'||style="text-align:right" data-sort-value="'..tree.interval..'"|'..Shared.timeString(tree.interval/1000, true)
result = result..'||style="text-align:right" data-sort-value="'..tree.baseInterval..'"|'..Shared.timeString(tree.baseInterval/1000, true)
local XPs = tree.xp / (tree.interval / 1000)
local XPs = tree.baseExperience / (tree.baseInterval / 1000)
local Log = Items.getItemByID(i - 1)
local Log = Items.getItemByID(tree.logID)
local GPs = Log.sellsFor / (tree.interval / 1000)
local GPs = Log.sellsFor / (tree.baseInterval / 1000)
result = result..'||style="text-align:right"|'..Shared.round(XPs, 2, 2)
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))
result = result..'||style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2))
Line 187: Line 163:


function p.getSpecialFishingTable(frame)
function p.getSpecialFishingTable(frame)
local lootValue = 0
local totalWt, lootValue = 0, 0
local totalWt = Items.specialFishWt
local itemArray = Shared.clone(SkillData.Fishing.SpecialItems)
for i, itemDef in ipairs(itemArray) do
totalWt = totalWt + itemDef[2]
end
-- Sort the loot table by weight in descending order
table.sort(itemArray, function(a, b) return (a[2] == b[2] and a[1] < b[1]) or a[2] > b[2] end)


local result = ''
local resultPart = {}
result = result..'\r\n{|class="wikitable sortable stickyHeader"'
table.insert(resultPart, '\r\n{|class="wikitable sortable stickyHeader"')
result = result..'\r\n|- class="headerRow-0"'
table.insert(resultPart, '\r\n|- class="headerRow-0"\r\n!colspan="2"| Item\r\n!Value\r\n!colspan="2"|Chance')
result = result..'\r\n!Item'
for i, itemDef in ipairs(itemArray) do
result = result..'!!Price!!colspan="2"|Chance'
local item = Items.getItemByID(itemDef[1])
 
if item ~= nil then
--Sort the loot table by weight in descending order
local dropChance = itemDef[2] / totalWt * 100
table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end)
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
for i, row in pairs(Items.specialFishLoot) do
local fmt = (dropChance < 0.10 and '%.2g') or '%.2f'
local thisItem = Items.getItemByID(row[1])
table.insert(resultPart, '\r\n|-\r\n|style="text-align:center"| ' .. Icons.Icon({item.name, type='item', notext=true}))
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor)))
result = result..'|'..Icons.GP(thisItem.sellsFor)
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. itemDef[2] .. '"| ' .. Shared.fraction(itemDef[2], totalWt))
 
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. string.format(fmt, dropChance) .. '%')
local dropChance = (row[2] / totalWt) * 100
lootValue = lootValue + (dropChance / 100 * item.sellsFor)
result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
end
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
end
result = result..'\r\n|}'
table.insert(resultPart, '\r\n|}\r\nThe average value of a roll on the special fishing loot table is ' .. Icons.GP(Shared.round(lootValue, 2, 0)))
result = result..'\r\nThe average value of a roll on the special fishing loot table is '..Icons.GP(Shared.round(lootValue, 2, 0))
return table.concat(resultPart)
 
return result
end
end


function p.getFishingJunkTable(frame)
function p.getFishingJunkTable(frame)
local result = '{| class="wikitable sortable stickyHeader"'
local resultPart = {}
result = result..'\r\n|- class="headerRow-0"'
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
result = result..'\r\n!colspan="2"|Item!!Value'
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!colspan="2"|Item!!Value')
local itemArray = Items.getItems(function(item) return item.type == "Junk" end)


local itemArray = {}
for i, itemID in ipairs(SkillData.Fishing.JunkItems) do
local item = Items.getItemByID(itemID)
if item ~= nil then
table.insert(itemArray, item)
end
end
table.sort(itemArray, function(a, b) return a.name < b.name end)
table.sort(itemArray, function(a, b) return a.name < b.name end)


for i, item in Shared.skpairs(itemArray) do
for i, item in ipairs(itemArray) do
result = result..'\r\n|-'
table.insert(resultPart, '\r\n|-')
result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext=true, size='50'})
table.insert(resultPart, '\r\n|style="min-width:25px"| ' .. Icons.Icon({item.name, type='item', notext=true, size=50}))
result = result..'||'..Icons.Icon({item.name, type='item', noicon=true})
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
result = result..'||style="text-align:right;" data-sort-value="'..item.sellsFor..'"|'..Icons.GP(item.sellsFor)
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor)))
end
end
 
table.insert(resultPart, '\r\n|}')
result = result..'\r\n|}'
return table.concat(resultPart)
 
return result
end
end


Line 251: Line 231:
result = result..'\r\n|-\r\n|style="min-width:25px"|'..Icons.Icon({ore.name, type='item', size='50', notext=true})
result = result..'\r\n|-\r\n|style="min-width:25px"|'..Icons.Icon({ore.name, type='item', size='50', notext=true})
result = result..'||'..Icons.Icon({ore.name, type='item', noicon=true})
result = result..'||'..Icons.Icon({ore.name, type='item', noicon=true})
result = result..'||style="text-align:right"|'..oreData.levelRequired..'||style="text-align:right"|'..ore.miningXP
result = result..'||style="text-align:right"|'..oreData.levelRequired..'||style="text-align:right"|'..oreData.baseExperience
result = result..'||style="text-align:right" data-sort-value="'..oreData.baseRespawnInterval..'"|'
result = result..'||style="text-align:right" data-sort-value="'..oreData.baseRespawnInterval..'"|'
result = result..Shared.timeString(oreData.baseRespawnInterval / 1000, true)
result = result..Shared.timeString(oreData.baseRespawnInterval / 1000, true)
Line 281: Line 261:


function p.getFishTable(frame)
function p.getFishTable(frame)
local data = Items.getItems(function(item) return item.fishingID ~= nil end)
local recipeList = {}
for i, recipe in ipairs(SkillData.Fishing.Fish) do
table.insert(recipeList, recipe)
end
table.sort(recipeList, function(a, b) return (a.level == b.level and a.masteryID < b.masteryID) or a.level < b.level end)


table.sort(data, function(a, b) return a.fishingID < b.fishingID end)
-- Determine cooking levels for all fish
local cookReq = {}
for i, recipe in ipairs(SkillData.Cooking.Recipes) do
-- This assumes that each raw fish only appears in a single recipe, which is a bit rubbish
-- but currently holds
for j, mat in ipairs(recipe.itemCosts) do
if cookReq[mat.id] == nil then
cookReq[mat.id] = recipe.level
end
end
end


local result = '{| class="wikitable sortable stickyHeader"'
local resultPart = {}
result = result..'\r\n|- class="headerRow-0"'
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
result = result..'\r\n!Fish\r\n!Name\r\n!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level\r\n!Catch Time'
table.insert(resultPart, '\r\n|- class="headerRow-0"')
result = result..'\r\n!Experience\r\n!Fish Price\r\n!XP/s\r\n!GP/s\r\n!'
table.insert(resultPart, '\r\n!Fish\r\n!Name\r\n!' .. Icons.Icon({'Fishing', type='skill', notext=true}) .. ' Level\r\n!Catch Time')
result = result..Icons.Icon({'Cooking', type='skill', notext=true})..' Level'
table.insert(resultPart, '\r\n!XP\r\n!Value\r\n!XP/s\r\n!GP/s')
 
table.insert(resultPart, '\r\n!' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' Level')
for i, fish in Shared.skpairs(data) do
for i, recipe in ipairs(recipeList) do
result = result..'\r\n|-'
local fish = Items.getItemByID(recipe.itemID)
result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', size='50', notext=true})
if fish ~= nil then
result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', noicon=true})
local timeSortVal = (recipe.baseMinInterval + recipe.baseMaxInterval) / 2000
result = result..'\r\n| style="text-align:right"|'..fish.fishingLevel
local timeStr = string.format("%.1fs - %.1fs", recipe.baseMinInterval / 1000, recipe.baseMaxInterval / 1000)
 
local XPs = recipe.baseXP / timeSortVal
local timeSortVal = (fish.minFishingInterval + fish.maxFishingInterval) / 2
local GPs = fish.sellsFor / timeSortVal
local timeStr = string.format("%.1fs-%.1fs", (fish.minFishingInterval/1000), (fish.maxFishingInterval/1000))
local cookSortVal = cookReq[recipe.itemID] or 0
result = result..'\r\n| style="text-align:right" data-sort-value="'..timeSortVal..'"|'..timeStr
local cookStr = cookReq[recipe.itemID] or 'N/A'
result = result..'\r\n| style="text-align:right"|'..fish.fishingXP
table.insert(resultPart, '\r\n|-')
result = result..'\r\n| style="text-align:right"|'..fish.sellsFor
table.insert(resultPart, '\r\n|style="text-align:center"| ' .. Icons.Icon({fish.name, type='item', size='50', notext=true}))
local XPs = fish.fishingXP / (timeSortVal / 1000)
table.insert(resultPart, '\r\n| ' .. Icons.Icon({fish.name, type='item', noicon=true}))
local GPs = fish.sellsFor / (timeSortVal / 1000)
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. recipe.level)
result = result..'\r\n| style="text-align:right"|'..Shared.round(XPs, 2, 2)
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. timeSortVal .. '"| ' .. timeStr)
result = result..'\r\n| style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2))
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. recipe.baseXP)
 
table.insert(resultPart, '\r\n|data-sort-value="' .. fish.sellsFor .. '"| ' .. Icons.GP(fish.sellsFor))
local cookStr = "N/A"
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. Shared.round(XPs, 2, 2))
if fish.cookingLevel ~= nil then
table.insert(resultPart, '\r\n|data-sort-value="' .. GPs .. '"|' .. Icons.GP(Shared.round(GPs, 2, 2)))
cookStr = fish.cookingLevel
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. cookSortVal .. '"| ' .. cookStr)
end
end
result = result..'\r\n| style="text-align:right"|'..cookStr
end
end
 
table.insert(resultPart, '\r\n|}')
result = result..'\r\n|}'
return table.concat(resultPart)
return result
end
end


function p.getFishingAreasTable(frame)
function p.getFishingAreasTable(frame)
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"'
Line 325: Line 316:
result = result..'\r\n!Junk Chance\r\n!Special Chance'
result = result..'\r\n!Junk Chance\r\n!Special Chance'


for i, area in Shared.skpairs(SkillData.Fishing.Areas) do
for i, area in ipairs(SkillData.Fishing.Areas) do
result = result..'\r\n|-'
result = result..'\r\n|-'
result = result..'\r\n| style ="text-align: left;" |'..area.name
result = result..'\r\n| style ="text-align: left;" |'..area.name


local fishArray = {}
local fishArray = {}
for j, fish in Shared.skpairs(area.fish) do
for j, fish in ipairs(area.fish) do
local fishTable = Items.getItems(function(item) return item.fishingID == fish end)
local fishItem = Items.getItemByID(fish.itemID)
local fishItem = fishTable[0] or fishTable[1]
if fishItem ~= nil then
table.insert(fishArray, Icons.Icon({fishItem.name, type='item'}))
table.insert(fishArray, Icons.Icon({fishItem.name, type='item'}))
end
end
end
result = result..'\r\n|'..table.concat(fishArray, '<br />')
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.fishChance..'%'
Line 344: Line 336:
result = result..'\r\n|}'
result = result..'\r\n|}'
return result
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._getThievingNPCStat(npc, statName)
local result = nil
if statName == 'level' then
result = Icons._SkillReq('Thieving', npc.level)
elseif statName == 'maxHit' then
result = npc.maxHit * 10
elseif statName == 'area' then
local area = p.getThievingNPCArea(npc)
result = area.name
else
result = npc[statName]
end
if result == nil then
result = ''
end
return result
end
function p.getThievingNPCStat(frame)
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
local statName = frame.args ~= nil and frame.args[2] or frame[2]
local npc = p.getThievingNPC(npcName)
if npc == nil then
return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
end
return p._getThievingNPCStat(npc, statName)
end
end


Line 413: Line 345:
local thisItem = Items.getItemByID(drop.itemID)
local thisItem = Items.getItemByID(drop.itemID)
local odds = drop.chance
local odds = drop.chance
 
rareTxt = rareTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
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..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
Line 426: Line 358:
local result = ''
local result = ''
local sectionTxt = {}
local sectionTxt = {}
 
--Five sections here: GP, normal loot, area loot, rare loot, and unique item
--Five sections here: GP, normal loot, area loot, rare loot, and unique item
--First up, GP:
--First up, GP:
local gpTxt = 'Successfully pickpocketing the '..npc.name..' will always give '..Icons.GP(1, npc.maxGP)
local gpTxt = 'Successfully pickpocketing the '..npc.name..' will always give '..Icons.GP(1, npc.maxGP)
table.insert(sectionTxt, gpTxt)
table.insert(sectionTxt, gpTxt)
 
--Next up, normal loot:
--Next up, normal loot:
--(Skip if no loot)
--(Skip if no loot)
Line 437: Line 369:
local normalTxt = '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:'
local normalTxt = '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:'
local totalWt = 0
local totalWt = 0
local lootChance = thievingNormalLootChance
local lootChance = SkillData.Thieving.ItemChance
local lootValue = 0
local lootValue = 0
 
--First loop through to get the total weight so we have it for later
--First loop through to get the total weight so we have it for later
for i, loot in pairs(npc.lootTable) do
for i, loot in pairs(npc.lootTable) do
totalWt = totalWt + loot[2]
totalWt = totalWt + loot[2]
end
end
 
normalTxt = normalTxt..'\r\n{|class="wikitable sortable"'
normalTxt = normalTxt..'\r\n{|class="wikitable sortable"'
normalTxt = normalTxt..'\r\n!Item!!Qty'
normalTxt = normalTxt..'\r\n!Item!!Qty'
normalTxt = normalTxt..'!!Price!!colspan="2"|Chance'
normalTxt = normalTxt..'!!Price!!colspan="2"|Chance'
 
--Then sort the loot table by weight
--Then sort the loot table by weight
table.sort(npc.lootTable, function(a, b) return a[2] > b[2] end)
table.sort(npc.lootTable, function(a, b) return a[2] > b[2] end)
Line 460: Line 392:
end
end
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..maxQty..'"|'
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..maxQty..'"|'
 
if maxQty > 1 then
if maxQty > 1 then
normalTxt = normalTxt.. '1 - '
normalTxt = normalTxt.. '1 - '
end
end
normalTxt = normalTxt..Shared.formatnum(row[3])
normalTxt = normalTxt..Shared.formatnum(row[3])
 
--Adding price columns
--Adding price columns
local itemPrice = 0
local itemPrice = 0
Line 478: Line 410:
end
end
end
end
 
--Getting the drop chance
--Getting the drop chance
local dropChance = (row[2] / totalWt * lootChance)
local dropChance = (row[2] / totalWt * lootChance)
Line 490: Line 422:
end
end
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
 
--Adding to the average loot value based on price & dropchance
--Adding to the average loot value based on price & dropchance
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
end
end
if multiDrop then
if Shared.tableCount(npc.lootTable) > 1 then
normalTxt = normalTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
normalTxt = normalTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
if lootChance < 100 then
if lootChance < 100 then
Line 501: Line 433:
normalTxt = normalTxt..'\r\n|colspan="2" '
normalTxt = normalTxt..'\r\n|colspan="2" '
end
end
normalTxt = normalTxt..'style="text-align:right"|'..lootChance..'.00%'
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(lootChance, 2, 2)..'%'
end
end
normalTxt = normalTxt..'\r\n|}'
normalTxt = normalTxt..'\r\n|}'
table.insert(sectionTxt, normalTxt)
table.insert(sectionTxt, normalTxt)
end
end
 
--After normal drops, add in rare drops
--After normal drops, add in rare drops
local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket'
local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket'
rareTxt = rareTxt..'\r\n'..p.getThievingGeneralRareTable()
rareTxt = rareTxt..'\r\n'..p.getThievingGeneralRareTable()
table.insert(sectionTxt, rareTxt)
table.insert(sectionTxt, rareTxt)
 
local areaTxt = '===Possible Area Unique Drops==='
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..'\r\nAny Area Unique Drop is equally likely to be obtained after a successful pickpocket. '
areaTxt = areaTxt..'\r\nEach Area Unique Drop is rolled for separately, so it is possible to receive multiple Area Unique Drops from a single action. '
areaTxt = areaTxt..'\r\nEach Area Unique Drop is rolled for separately, so it is possible to receive multiple Area Unique Drops from a single action. '
areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.'
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)
local area = Skills.getThievingNPCArea(npc)
areaTxt = areaTxt..'\r\n{|class="wikitable sortable"'
areaTxt = areaTxt..'\r\n{|class="wikitable sortable"'
areaTxt = areaTxt..'\r\n!Item!!Qty'
areaTxt = areaTxt..'\r\n!Item!!Qty'
Line 528: Line 460:
lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
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..'||'..drop.qty..'||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(1, 1/(thievingAreaLootChance/100))
lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(1, 1/(SkillData.Thieving.AreaUniqueChance/100))
lineTxt = lineTxt..'||'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
lineTxt = lineTxt..'||'..Shared.round(SkillData.Thieving.AreaUniqueChance, 2, 2)..'%'
dropLines[thisItem.name] = lineTxt
dropLines[thisItem.name] = lineTxt
end
end
Line 536: Line 468:
end
end
areaTxt = areaTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
areaTxt = areaTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
areaTxt = areaTxt..'\r\n|style="text-align:right"|'..Shared.fraction(1, 1/(thievingAreaLootChance/100))..'||'
areaTxt = areaTxt..'\r\n|style="text-align:right"|'..Shared.fraction(1, 1/(SkillData.Thieving.AreaUniqueChance/100))..'||'
areaTxt = areaTxt..'style="text-align:right"|'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
areaTxt = areaTxt..'style="text-align:right"|'..Shared.round(SkillData.Thieving.AreaUniqueChance, 2, 2)..'%'
areaTxt = areaTxt..'\r\n|}'
areaTxt = areaTxt..'\r\n|}'
table.insert(sectionTxt, areaTxt)
table.insert(sectionTxt, areaTxt)
 
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
local uniqueTxt = '===Possible NPC Unique Drop==='
local uniqueTxt = '===Possible NPC Unique Drop==='
Line 552: Line 484:
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'})
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'})
end
end
 
table.insert(sectionTxt, uniqueTxt)
table.insert(sectionTxt, uniqueTxt)
end
end
 
return table.concat(sectionTxt, '\r\n')
return table.concat(sectionTxt, '\r\n')
end
end
Line 561: Line 493:
function p.getThievingNPCLootTables(frame)
function p.getThievingNPCLootTables(frame)
local npcName = frame.args ~= nil and frame.args[1] or frame
local npcName = frame.args ~= nil and frame.args[1] or frame
local npc = p.getThievingNPC(npcName)
local npc = Skills.getThievingNPC(npcName)
if npc == nil then
if npc == nil then
return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
end
end
 
return p._getThievingNPCLootTables(npc)
return p._getThievingNPCLootTables(npc)
end
end
Line 580: Line 512:
result = result..'||'..Icons.Icon({npc.name, type='thieving', noicon=true})
result = result..'||'..Icons.Icon({npc.name, type='thieving', noicon=true})


local area = p.getThievingNPCArea(npc)
local area = Skills.getThievingNPCArea(npc)
result = result..'||'..area.name
result = result..'||'..area.name
result = result..'||'..Icons._SkillReq('Thieving', npc.level)
result = result..'||'..Icons._SkillReq('Thieving', npc.level)
Line 599: Line 531:
end
end
result = result..'\r\n|}'
result = result..'\r\n|}'
 
return result
return result
end
end
Line 608: Line 540:
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops')
table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops')
 
local areaArray = Shared.clone(SkillData.Thieving.Areas)
local areaArray = Shared.clone(SkillData.Thieving.Areas)
table.sort(areaArray, function(a, b) return a.id < b.id end)
table.sort(areaArray, function(a, b) return a.id < b.id end)
Line 627: Line 559:
table.insert(npcList, '')
table.insert(npcList, '')
end
end
 
-- Build area unique item list
-- Build area unique item list
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
Line 645: Line 577:
table.insert(areaItemList, '')
table.insert(areaItemList, '')
end
end
 
-- Generate table row
-- Generate table row
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|-')
Line 654: Line 586:
end
end
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')
 
return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.getThievingSourcesForItem(itemID)
function p._getFarmingTable(category)
local resultArray = {}
local seedList = {}
if category == 'Allotment' or category == 'Herb' or category == 'Tree' then
local areaNPCs = {}
seedList = Items.getItems(function(item) return item.tier == category end)
else
--First check area unique drops
return 'ERROR: Invalid farming category. Please choose Allotment, Herb, or Tree'
--If an area drops the item, add all the NPC ids to the list so we can add them later
end
if not result then
 
for i, area in pairs(SkillData.Thieving.Areas) do
local result = '{|class="wikitable sortable stickyHeader"'
for j, drop in pairs(area.uniqueDrops) do
result = result..'\r\n|- class="headerRow-0"'
if drop.itemID == itemID then
result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
for k, npcID in pairs(area.npcs) do
result = result..'!!XP!!Growth Time!!Seed Value'
areaNPCs[npcID] = drop.qty
if category == 'Allotment' then
end
result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
break
elseif category == 'Herb' then
end
result = result..'!!colspan="2"|Herb!!Herb Value'
end
elseif category == 'Tree' then
end
result = result..'!!colspan="2"|Logs!!Log Value'
end
end
result = result..'!!Seed Sources'
--Now go through and get drop chances on each NPC if needed
 
for i, npc in pairs(SkillData.Thieving.NPCs) do
table.sort(seedList, function(a, b) return a.farmingLevel < b.farmingLevel end)
local totalWt = 0
 
local dropWt = 0
for i, seed in pairs(seedList) do
local dropQty = 0
result = result..'\r\n|-'
for j, drop in pairs(npc.lootTable) do
result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
totalWt = totalWt + drop[2]
result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
if drop[1] == itemID then
result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
dropWt = drop[2]
result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)
dropQty = drop[3]
 
end
local crop = Items.getItemByID(seed.grownItemID)
end
result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
if dropWt > 0 then
if category == 'Allotment' then
table.insert(resultArray, {npc = npc.name, minQty = 1, maxQty = dropQty, wt = dropWt * thievingNormalLootChance, totalWt = totalWt * 100, level = npc.level})
result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
end
--Chance of -1 on unique drops is to indicate variable chance
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID == itemID then
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.qty, maxQty = npc.uniqueDrop.qty, wt = -1, totalWt = -1, level = npc.level})
end
if areaNPCs[npc.id] ~= nil then
table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = thievingAreaLootChance, totalWt = 100, level = npc.level})
end
end
result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
result = result..'||'..ItemSourceTables._getItemSources(seed)
end
end
 
for i, drop in pairs(SkillData.Thieving.RareItems) do
result = result..'\r\n|}'
if drop.itemID == itemID then
return result
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1})
end
 
function p.getFarmingTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
 
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
end
end
 
return resultArray
result = result..'\r\n|}'
 
return result
end
end


-- For a given constellation cons and modifier value modValue, generates and returns
function p.getFarmingPlotTable(frame)
-- a table of modifiers, much like any other item/object elsewhere in the game.
local areaName = frame.args ~= nil and frame.args[1] or frame
-- includeStandard: true|false, determines whether standard modifiers are included
local patches = nil
-- includeUnique: true|false, determines whether unique modifiers are included
for i, area in Shared.skpairs(SkillData.Farming.Patches) do
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated
if area.areaName == areaName then
-- asKeyValue: true|false, if true, returns key/value pairs like usual modifier objects
patches = area.patches
function p._buildAstrologyModifierArray(cons, modValue, includeStandard, includeUnique, isDistinct, asKeyValue)
break
-- Temporary function to determine if the table already contains a given modifier
local containsMod = function(modList, modNew)
for i, modItem in ipairs(modList) do
-- Check mod names & value data types both equal
if modItem[1] == modNew[1] and type(modItem[2]) == type(modNew[2]) then
if type(modItem[2]) == 'table' then
if Shared.tablesEqual(modItem[2], modNew[2]) then
return true
end
elseif modItem[2] == modNew[2] then
return true
end
end
end
return false
end
local addToArray = function(modArray, modNew)
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
table.insert(modArray, modNew)
end
end
local modArray = {}
local isSkillMod = {}
-- Standard modifiers
if includeStandard then
for i, skillMods in ipairs(cons.standardModifiers) do
local skillID = cons.skills[i]
if skillID ~= nil then
for j, modName in ipairs(skillMods) do
local modBaseName, modText, sign, isNegative, unsign, modBase = Constants.getModifierDetails(modName)
-- Check if modifier varies by skill, and amend the modifier value accordingly
local modVal = modValue
if Shared.contains(modText, '{SV0}') then
isSkillMod[modName] = true
modVal = {skillID, modValue}
end
addToArray(modArray, {modName, modVal})
end
end
end
end
end
end
-- Unique modifiers
if patches == nil then
if includeUnique then
return "ERROR: Invalid area name.[[Category:Pages with script errors]]"
local skillArray = {}
for i, skillID in ipairs(cons.skills) do
table.insert(skillArray, SkillData.Skills[skillID + 1])
end
for i, modName in ipairs(cons.uniqueModifiers) do
-- The most important thing we're getting here is the modText and modBase
-- modText lets us check if this is a per-skill modifier or not
-- modBase lets us check .isSkill, which are modifiers that we only use for skills with Mastery
local modBaseName, modText, sign, isNegative, unsign, modBase = Constants.getModifierDetails(modName)
if Shared.contains(modText, '{SV0}') then
isSkillMod[modName] = true
-- Check which skills the current modifier can be used for
for j, skillID in ipairs(cons.skills) do
if not modBase.isSkill or (modBase.isSkill and skillArray[j].hasMastery) then
addToArray(modArray, {modName, {skillID, modValue}})
end
end
else
addToArray(modArray, {modName, modValue})
end
end
end
end
 
if asKeyValue then
local result = '{|class="wikitable"'
local modArrayKV = {}
result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
for i, modDefn in ipairs(modArray) do
 
local modName, modVal = modDefn[1], modDefn[2]
for i, patch in Shared.skpairs(patches) do
local isSkill = isSkillMod[modName]
result = result..'\r\n|-\r\n|'..i
if modArrayKV[modName] == nil then
result = result..'||style="text-align:right;" data-sort-value="' .. patch.level .. '"|'..patch.level
modArrayKV[modName] = (isSkill and { modVal } or modVal)
if patch.cost == 0 then
elseif isSkill then
result = result..'||Free'
table.insert(modArrayKV[modName], modVal)
else
else
result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
modArrayKV[modName] = modArrayKV[modName] + modVal
end
end
end
return modArrayKV
else
return modArray
end
end
result = result..'\r\n|}'
return result
end
end


Line 814: Line 701:
result = result..'\r\n!colspan="2"|Constellation!!'..Icons.Icon({"Astrology", type='skill', notext='true'})..' Level'
result = result..'\r\n!colspan="2"|Constellation!!'..Icons.Icon({"Astrology", type='skill', notext='true'})..' Level'
result = result..'!!XP!!Skills!!Standard Modifiers!!Unique Modifiers'
result = result..'!!XP!!Skills!!Standard Modifiers!!Unique Modifiers'
 
for i, cons in Shared.skpairs(SkillData.Astrology.Constellations) do
for i, cons in ipairs(SkillData.Astrology.Constellations) do
local name = cons.name
local name = cons.name
result = result..'\r\n|-'
result = result..'\r\n|-'
result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='constellation', size='50', notext=true})..'||'..name
result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='constellation', size='50', notext=true})..'||'..name
result = result..'||'..cons.level..'||'..cons.provides.xp
result = result..'||'..cons.level..'||'..cons.provides.xp
 
local skillIconArray = {}
local skillIconArray = {}
for j, skillID in ipairs(cons.skills) do
for j, skillID in ipairs(cons.skills) do
Line 826: Line 713:
end
end
result = result..'||'..table.concat(skillIconArray, '<br/>')
result = result..'||'..table.concat(skillIconArray, '<br/>')
 
local standModsRaw = p._buildAstrologyModifierArray(cons, maxModifier, true, false, false, false)
local standModsRaw = Skills._buildAstrologyModifierArray(cons, maxModifier, true, false, false, false)
local standMods = {}
local standMods = {}
--Building the list of Standard modifiers:
--Building the list of Standard modifiers:
Line 834: Line 721:
end
end
result = result..'|| '..table.concat(standMods, '<br/>')
result = result..'|| '..table.concat(standMods, '<br/>')
 
--Building the list of all Unique Modifiers
--Building the list of all Unique Modifiers
local uModsRaw = p._buildAstrologyModifierArray(cons, maxModifier, false, true, false, false)
local uModsRaw = Skills._buildAstrologyModifierArray(cons, maxModifier, false, true, false, false)
local uMods = {}
local uMods = {}
for j, modifier in ipairs(uModsRaw) do
for j, modifier in ipairs(uModsRaw) do
Line 844: Line 731:
end
end
result = result..'\r\n|}'
result = result..'\r\n|}'
 
return result
return result
end
end
Line 856: Line 743:
result = result..'\r\n!rowspan="2"| Value!!colspan="2"| Chance'
result = result..'\r\n!rowspan="2"| Value!!colspan="2"| Chance'
result = result..'\r\n|-\r\n! This Value!! This Value or Greater'
result = result..'\r\n|-\r\n! This Value!! This Value or Greater'
local lastChance = 0
local cumulativeChance = 100
local cumulativeChance = 100
for i, chance in Shared.skpairs(SkillData.Astrology.Defaults.valueWeight) do
for i, chance in ipairs(SkillData.Astrology.ModifierMagnitudeChances) do
local thisChance = (i == 5 and chance) or chance - lastChance
result = result..'\r\n|-'
result = result..'\r\n|-'
result = result..'\r\n|style="text-align:right"| '..i
result = result..'\r\n|style="text-align:right"| ' .. i
result = result..'\r\n|style="text-align:right"| ' .. thisChance .. '%'
result = result..'\r\n|style="text-align:right"| ' .. chance .. '%'
result = result..'\r\n|style="text-align:right"| ' .. cumulativeChance .. '%'
result = result..'\r\n|style="text-align:right"| ' .. cumulativeChance .. '%'
cumulativeChance = cumulativeChance - thisChance
cumulativeChance = cumulativeChance - chance
lastChance = chance
end
end
result = result..'\r\n|}'
result = result..'\r\n|}'