Anonymous

Module:Skills/Gathering: Difference between revisions

From Melvor Idle
_getThievingNPCLootTables: Remove area unique drops total, as each drop can (theoretically) be obtained from the same pickpocket attempt
(getMiningOresTable: Further fixes for v1.0.2 data)
(_getThievingNPCLootTables: Remove area unique drops total, as each drop can (theoretically) be obtained from the same pickpocket attempt)
(40 intermediate revisions by 3 users not shown)
Line 1: Line 1:
--Splitting some functions into here to avoid bloating a single file
--Splitting some functions into here to avoid bloating a single file
local p = {}
local p = {}
local SkillData = mw.loadData('Module:Skills/data')
local ShopData = mw.loadData('Module:Shop/data')


local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Items = require('Module:Items')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Shop = require('Module:Shop')
local Skills = require('Module:Skills')
local ItemSourceTables = require('Module:Items/SourceTables')


local thievingNormalLootChance = 75
function p.getRecipeRequirements(skillName, recipe)
local thievingAreaLootChance = 0.2
local reqText = {}
 
if recipe.level ~= nil then
function p.getConstellationByID(constID)
table.insert(reqText, Icons._SkillReq(skillName, recipe.level, false))
return SkillData.Astrology.Constellations[constID]
end
end
if recipe.totalMasteryRequired ~= nil then
 
table.insert(reqText, Shared.formatnum(recipe.totalMasteryRequired) .. ' ' .. Icons.Icon({skillName, type='skill', notext=true}) .. ' ' .. Icons.Icon({'Mastery'}))
function p.getConstellation(constName)
for i, const in ipairs(SkillData.Astrology.Constellations) do
if const.name == constName then
return const
end
end
end
return nil
if recipe.shopItemPurchased ~= nil then
end
local purchReq = Shop.getPurchaseByID(recipe.shopItemPurchased)
 
if purchReq ~= nil then
function p.getConstellations(checkFunc)
table.insert(reqText, Shop._getPurchaseIcon({purchReq}))
local result = {}
for i, const in ipairs(SkillData.Astrology.Constellations) do
if checkFunc(const) then
table.insert(result, const)
end
end
end
end
return result
return table.concat(reqText, '<br/>')
end
end


function p.getAxeTable(frame)
function p.getTreesTable(frame)
local toolArray = {}
local resultPart = {}
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
table.insert(resultPart, '{| class="wikitable sortable"')
if Shared.contains(upgrade.name, 'Axe') then
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(toolArray, upgrade)
table.insert(resultPart, '\n!colspan="2"|Tree!!colspan="2"|Logs!!Requirements')
end
table.insert(resultPart, '!!XP!!Cut Time!!XP/s!!GP/s')
end


local result = '{| class="wikitable"'
for i, tree in ipairs(SkillData.Woodcutting.trees) do
result = result..'\r\n!colspan="4"| !!colspan="2"|Cut Time Decrease'
local log = Items.getItemByID(tree.productId)
result = result..'\r\n|- class="headerRow-0"'
table.insert(resultPart, '\n|-')
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level'
table.insert(resultPart, '\n|class="table-img" data-sort-value="'..tree.name..'"| '..Icons.Icon({log.name, img=tree.name, type='tree', notext=true, size=50}))
result = result..'!!Cost!!This Axe!!Total'
table.insert(resultPart, '\n|data-sort-value="'..tree.name..'"|'..Icons.getExpansionIcon(tree.id)..tree.name)
 
table.insert(resultPart, '\n|class="table-img" data-sort-value="'..log.name..'"| '..Icons.Icon({log.name, type='item', notext=true, size=50}))
local total = 0
table.insert(resultPart, '\n| '..Icons.Icon({log.name, type='item', noicon=true}))
 
table.insert(resultPart, '\n|data-sort-value="' .. tree.level .. '"| ' .. p.getRecipeRequirements(SkillData.Woodcutting.name, tree))
for i, tool in Shared.skpairs(toolArray) do
table.insert(resultPart, '\n|style="text-align:right"| '..tree.baseExperience)
result = result..'\r\n|-'
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="'..tree.baseInterval..'"| '..Shared.timeString(tree.baseInterval/1000, true))
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
local XPs = tree.baseExperience / (tree.baseInterval / 1000)
result = result..'||'..tool.name
local GPs = log.sellsFor / (tree.baseInterval / 1000)
local level = 1
table.insert(resultPart, '\n|style="text-align:right"| '..Shared.round(XPs, 2, 2))
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="'..GPs..'"| '..Icons.GP(Shared.round(GPs, 2, 2)))
--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
end


result = result..'\r\n|}'
table.insert(resultPart, '\n|}')
return result
return table.concat(resultPart)
end
end


function p.getPickaxeTable(frame)
function p.getSpecialFishingTable(frame)
local toolArray = {}
local totalWt, lootValue = 0, 0
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
local itemArray = GameData.getEntities(SkillData.Fishing.specialItems, function(item) return true end)
if Shared.contains(upgrade.name, 'Pickaxe') then
for i, itemDef in ipairs(itemArray) do
table.insert(toolArray, upgrade)
totalWt = totalWt + itemDef.weight
end
end
end
-- Sort the loot table by weight in descending order
table.sort(itemArray, function(a, b) return a.weight > b.weight end)


local result = '{| class="wikitable"'
local resultPart = {}
result = result..'\r\n!colspan="4"| !!colspan="2"|Mine Time Decrease!!colspan="2"|2x Ore Chance'
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!colspan="2"|Name!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
for i, itemDef in ipairs(itemArray) do
result = result..'!!Cost!!This Pick!!Total!!This Pick!!Total'
local item = Items.getItemByID(itemDef.itemID)
 
if item ~= nil then
local total = 0
local dropChance = itemDef.weight / totalWt * 100
local total2 = 0
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
 
local fmt = (dropChance < 0.10 and '%.2g') or '%.2f'
for i, tool in Shared.skpairs(toolArray) do
table.insert(resultPart, '\r\n|-\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true}))
result = result..'\r\n|-'
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor)))
result = result..'||'..tool.name
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. itemDef.weight .. '"| ' .. Shared.fraction(itemDef.weight, totalWt))
local level = 1
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. string.format(fmt, dropChance) .. '%')
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
lootValue = lootValue + (dropChance / 100 * item.sellsFor)
--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
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
end
 
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\n|}'
return table.concat(resultPart)
return result
end
end


function p.getRodTable(frame)
function p.getFishingJunkTable(frame)
local toolArray = {}
local resultPart = {}
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
if Shared.contains(upgrade.name, 'Fishing Rod') then
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(toolArray, upgrade)
table.insert(resultPart, '\r\n!colspan="2"|Item!!Value')
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
local itemArray = {}
 
for i, itemID in ipairs(SkillData.Fishing.junkItemIDs) do
for i, tool in Shared.skpairs(toolArray) do
local item = Items.getItemByID(itemID)
result = result..'\r\n|-'
if item ~= nil then
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
table.insert(itemArray, item)
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
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
end
table.sort(itemArray, function(a, b) return a.name < b.name end)


result = result..'\r\n|}'
for i, item in ipairs(itemArray) do
return result
table.insert(resultPart, '\r\n|-')
end
table.insert(resultPart, '\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true, size=50}))
 
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
function p.getTreesTable(frame)
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor)))
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..'||'..Icons.Icon({logName, type='item', noicon=true})
result = result..'||style="text-align:right"|'..tree.levelRequired
result = result..'||style="text-align:right"|'..tree.baseExperience
result = result..'||style="text-align:right" data-sort-value="'..tree.baseInterval..'"|'..Shared.timeString(tree.baseInterval/1000, true)
local XPs = tree.baseExperience / (tree.baseInterval / 1000)
local Log = Items.getItemByID(tree.logID)
local GPs = Log.sellsFor / (tree.baseInterval / 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
end
 
table.insert(resultPart, '\r\n|}')
result = result..'\r\n|}'
return table.concat(resultPart)
return result
end
end


function p.getSpecialFishingTable(frame)
function p.getMiningOresTable(frame)
local lootValue = 0
local resultPart = {}
local totalWt = Items.specialFishWt
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
 
table.insert(resultPart, '\n|- class="headerRow-0"')
local result = ''
table.insert(resultPart, '\n!colspan="2"|Rock!!colspan=2|Ore!!Type!!Requirements')
result = result..'\r\n{|class="wikitable sortable stickyHeader"'
table.insert(resultPart, '!!XP!!Respawn Time!!Ore Value')
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
local mineData = GameData.getEntities(SkillData.Mining.rockData, function(rock) return true end)
table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end)
table.sort(mineData, function(a, b) return a.level < b.level 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
for i, oreData in ipairs(mineData) do
result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
local ore = Items.getItemByID(oreData.productId)
result = result..'|'..Shared.fraction(row[2], totalWt)
local respawnStyle, respawnSort, respawnText = 'class="table-na"', 0, 'N/A'
result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
if oreData.hasPassiveRegen then
lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor)
respawnStyle = 'style="text-align:right;'
respawnSort = oreData.baseRespawnInterval / 1000
respawnText = Shared.timeString(respawnSort, true)
end
local rockName = Icons.Icon({oreData.name, type='rock', noicon=true, nolink=true})
local qtyText = (oreData.baseQuantity > 1 and '<b>' .. oreData.baseQuantity .. 'x</b> ') or ''
table.insert(resultPart, '\n|-\n|class="table-img" data-sort-value="' .. rockName .. '"| '..Icons.Icon({oreData.name, type='rock', size='50', notext=true, nolink=true}))
table.insert(resultPart, '\n| data-sort-value="' ..rockName.. '"|'..Icons.getExpansionIcon(oreData.id) .. rockName)
table.insert(resultPart, '\n|class="table-img" data-sort-value="' .. ore.name .. '"| '..Icons.Icon({ore.name, type='item', size='50', notext=true}))
table.insert(resultPart, '\n| ' .. qtyText .. Icons.Icon({ore.name, type='item', noicon=true}))
table.insert(resultPart, '\n| ' .. oreData.type)
table.insert(resultPart, '\n|data-sort-value="' .. oreData.level ..'"| '..p.getRecipeRequirements(SkillData.Mining.name, oreData))
table.insert(resultPart, '\n|style="text-align:right"| '..oreData.baseExperience)
table.insert(resultPart, '\n|' .. respawnStyle .. ' data-sort-value="'..respawnSort..'"| ' .. respawnText)
table.insert(resultPart, '\n|data-sort-value="'..ore.sellsFor..'"| '..Icons.GP(ore.sellsFor))
end
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
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
end


function p.getFishingJunkTable(frame)
function p._getMiningGemsTable(gemType)
local result = '{| class="wikitable sortable stickyHeader"'
if type(gemType) ~= 'string' then
result = result..'\r\n|- class="headerRow-0"'
gemType = 'Normal'
result = result..'\r\n!colspan="2"|Item!!Value'
end
 
local validTypes = {
local itemArray = Items.getItems(function(item) return item.type == "Junk" end)
["Normal"] = 'randomGems',
 
["Superior"] = 'randomSuperiorGems'
table.sort(itemArray, function(a, b) return a.name < b.name end)
}
 
local gemDataKey = validTypes[gemType]
for i, item in Shared.skpairs(itemArray) do
if gemDataKey == nil then
result = result..'\r\n|-'
return 'ERROR: No such gem type "' .. gemType .. '"[[Category:Pages with script errors]]'
result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext=true, size='50'})
end
result = result..'||'..Icons.Icon({item.name, type='item', noicon=true})
result = result..'||style="text-align:right;" data-sort-value="'..item.sellsFor..'"|'..Icons.GP(item.sellsFor)
local gemData = GameData.rawData[gemDataKey]
local totalWeight = 0
for i, gem in ipairs(gemData) do
totalWeight = totalWeight + gem.weight
end
end
local resultPart = {}
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan=2|Gem!!Gem Chance!!Gem Price')


result = result..'\r\n|}'
for i, gem in ipairs(gemData) do
 
local gemItem = Items.getItemByID(gem.itemID)
return result
local gemPct = gem.weight / totalWeight * 100
end
table.insert(resultPart, '\n|-\n|class="table-img"| ')
 
table.insert(resultPart, Icons.Icon({gemItem.name, type='item', size='50', notext=true}))
function p.getMiningOresTable(frame)
table.insert(resultPart, '\n| data-sort-value="'..gemItem.name..'"|'..Icons.getExpansionIcon(gemItem.id) .. Icons.Icon({gemItem.name, type='item', noicon=true}))
local result = '{|class="wikitable sortable stickyHeader"'
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. gemPct .. '" | ' .. string.format("%.1f%%", gemPct))
result = result..'\r\n|- class="headerRow-0"'
table.insert(resultPart, '\n|data-sort-value="' .. gemItem.sellsFor .. '"| ' .. Icons.GP(gemItem.sellsFor))
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.levelRequired < b.levelRequired end)
 
for i, oreData in Shared.skpairs(mineData) do
local ore = Items.getItemByID(oreData.oreID)
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..'||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..Shared.timeString(oreData.baseRespawnInterval / 1000, true)
result = result..'||data-sort-value="'..ore.sellsFor..'"|'..Icons.GP(ore.sellsFor)
end
end


result = result..'\r\n|}'
table.insert(resultPart, '\n|}')
return result
return table.concat(resultPart)
end
end


function p.getMiningGemsTable(frame)
function p.getMiningGemsTable(frame)
local result = '{|class="wikitable sortable stickyHeader"'
local gemType = frame.args ~= nil and frame.args[1] or frame
result = result..'\r\n|- class="headerRow-0"'
return p._getMiningGemsTable(gemType)
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})
result = result..'||'..Icons.Icon({gem.name, type='item', noicon=true})
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)
local recipeList = GameData.getEntities(SkillData.Fishing.fish, function(fish) return true end)
table.sort(recipeList, function(a, b) return 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.productId)
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.baseExperience / 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.productId] or 0
result = result..'\r\n| style="text-align:right" data-sort-value="'..timeSortVal..'"|'..timeStr
local cookStyle = (cookReq[recipe.productId] ~= nil and 'style="text-align:right" ' or 'class="table-na" ')
result = result..'\r\n| style="text-align:right"|'..fish.fishingXP
local cookStr = cookReq[recipe.productId] or 'N/A'
result = result..'\r\n| style="text-align:right"|'..fish.sellsFor
table.insert(resultPart, '\r\n|-')
local XPs = fish.fishingXP / (timeSortVal / 1000)
table.insert(resultPart, '\r\n|class="table-img"| ' .. Icons.Icon({fish.name, type='item', size='50', notext=true}))
local GPs = fish.sellsFor / (timeSortVal / 1000)
table.insert(resultPart, '\r\n|data-sort-value="'..fish.name..'"|'..Icons.getExpansionIcon(fish.id) .. Icons.Icon({fish.name, type='item', noicon=true}))
result = result..'\r\n| style="text-align:right"|'..Shared.round(XPs, 2, 2)
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. recipe.level)
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" data-sort-value="' .. timeSortVal .. '"| ' .. timeStr)
 
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. recipe.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience))
local cookStr = "N/A"
table.insert(resultPart, '\r\n|data-sort-value="' .. fish.sellsFor .. '"| ' .. Icons.GP(fish.sellsFor))
if fish.cookingLevel ~= nil then
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. Shared.round(XPs, 2, 2))
cookStr = fish.cookingLevel
table.insert(resultPart, '\r\n|data-sort-value="' .. GPs .. '"|' .. Icons.GP(Shared.round(GPs, 2, 2)))
table.insert(resultPart, '\r\n|' .. cookStyle .. '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 244:
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, fishID in ipairs(area.fishIDs) do
local fishTable = Items.getItems(function(item) return item.fishingID == fish end)
local fishItem = Items.getItemByID(fishID)
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 346: Line 266:
end
end


function p.getThievingNPC(npcName)
function p.getThievingGeneralRareTable(frame)
local result = nil
return p._getThievingGeneralRareTable()
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
end


function p.getThievingNPCStat(frame)
function p._getThievingGeneralRareTable(npcID)
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
 
function p.getThievingGeneralRareTable(frame)
local rareTxt = '{|class="wikitable sortable"'
local rareTxt = '{|class="wikitable sortable"'
rareTxt = rareTxt..'\r\n!Item!!Qty'
rareTxt = rareTxt..'\r\n!Item!!Qty'
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance'
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance'
for i, drop in pairs(SkillData.Thieving.RareItems) do
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
local thisItem = Items.getItemByID(drop.itemID)
-- If an npcID has been passed and the item is NPC specific, only display
local odds = drop.chance
-- the item if it may be obtained while pickpocketing that NPC
 
if npcID == nil or drop.npcs == nil or Shared.contains(drop.npcs, npcID) then
rareTxt = rareTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
local thisItem = Items.getItemByID(drop.itemID)
rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
local odds = drop.chance
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(1, Shared.round2(1/(odds/100), 0))
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.round(odds, 4, 4)..'%'
rareTxt = rareTxt..'\r\n|-\r\n|data-sort-value="'..thisItem.name..'"|'..Icons.getExpansionIcon(thisItem.id)..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(1, Shared.round2(1/(odds/100), 0))
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.round(odds, 4, 4)..'%'
end
end
end
rareTxt = rareTxt..'\r\n|}'
rareTxt = rareTxt..'\r\n|}'
Line 435: Line 303:
--(Skip if no loot)
--(Skip if no loot)
if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then
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 normalTxt = {}
table.insert(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.weight
end
end


normalTxt = normalTxt..'\r\n{|class="wikitable sortable"'
table.insert(normalTxt, '\r\n{|class="wikitable sortable"')
normalTxt = normalTxt..'\r\n!Item!!Qty'
table.insert(normalTxt, '\r\n!Item!!Qty')
normalTxt = normalTxt..'!!Price!!colspan="2"|Chance'
table.insert(normalTxt, '!!Price!!colspan="2"|Chance')


local lootTable = GameData.getEntities(npc.lootTable, function(loot) return true end)
--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(lootTable, function(a, b) return a.weight > b.weight end)
for i, row in Shared.skpairs(npc.lootTable) do
for i, loot in ipairs(lootTable) do
local thisItem = Items.getItemByID(row[1])
local thisItem = Items.getItemByID(loot.itemID)
local maxQty = row[3]
if thisItem ~= nil then
if thisItem ~= nil then
normalTxt = normalTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
table.insert(normalTxt, '\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}))
else
else
normalTxt = normalTxt..'\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]'
table.insert(normalTxt, '\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]')
end
end
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..maxQty..'"|'
table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..(loot.minQuantity + loot.maxQuantity)..'"|')


if maxQty > 1 then
if loot.minQuantity ~= loot.maxQuantity then
normalTxt = normalTxt.. '1 - '
table.insert(normalTxt, Shared.formatnum(loot.minQuantity) .. ' - ' .. Shared.formatnum(loot.maxQuantity))
else
table.insert(normalTxt, Shared.formatnum(loot.maxQuantity))
end
end
normalTxt = normalTxt..Shared.formatnum(row[3])


--Adding price columns
--Adding price columns
local itemPrice = 0
local itemPrice = 0
if thisItem == nil then
if thisItem == nil then
normalTxt = normalTxt..'||data-sort-value="0"|???'
table.insert(normalTxt, '||data-sort-value="0"|???')
else
else
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
if itemPrice == 0 or maxQty == 1 then
if itemPrice == 0 or loot.minQuantity == loot.maxQuantity then
normalTxt = normalTxt..'||'..Icons.GP(itemPrice)
table.insert(normalTxt, '||'..Icons.GP(itemPrice))
else
else
normalTxt = normalTxt..'||'..Icons.GP(itemPrice, itemPrice * maxQty)
table.insert(normalTxt, '||'..Icons.GP(itemPrice * loot.minQuantity, itemPrice * loot.maxQuantity))
end
end
end
end


--Getting the drop chance
--Getting the drop chance
local dropChance = (row[2] / totalWt * lootChance)
local dropChance = (loot.weight / totalWt * lootChance)
if dropChance ~= 100 then
if dropChance < 100 then
--Show fraction as long as it isn't going to be 1/1
--Show fraction as long as it isn't going to be 1/1
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..row[2]..'"'
table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..loot.weight..'"')
normalTxt = normalTxt..'|'..Shared.fraction(row[2] * lootChance, totalWt * 100)
table.insert(normalTxt, '|'..Shared.fraction(loot.weight * lootChance, totalWt * 100))
normalTxt = normalTxt..'||'
table.insert(normalTxt, '||')
else
else
normalTxt = normalTxt..'||colspan="2" data-sort-value="'..row[2]..'"'
table.insert(normalTxt, '||colspan="2" data-sort-value="'..loot.weight..'"')
end
end
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
table.insert(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 * (loot.minQuantity + loot.maxQuantity) / 2)
end
end
if Shared.tableCount(npc.lootTable) > 1 then
if Shared.tableCount(npc.lootTable) > 1 then
normalTxt = normalTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
table.insert(normalTxt, '\r\n|-class="sortbottom" \r\n!colspan="3"|Total:')
if lootChance < 100 then
if lootChance < 100 then
normalTxt = normalTxt..'\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||'
table.insert(normalTxt, '\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||')
else
else
normalTxt = normalTxt..'\r\n|colspan="2" '
table.insert(normalTxt, '\r\n|colspan="2" ')
end
end
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(lootChance, 2, 2)..'%'
table.insert(normalTxt, 'style="text-align:right"|'..Shared.round(lootChance, 2, 2)..'%')
end
end
normalTxt = normalTxt..'\r\n|}'
table.insert(normalTxt, '\r\n|}')
table.insert(sectionTxt, normalTxt)
table.insert(normalTxt, '\r\nThe loot obtained from the average successful pickpocket is worth ' .. Icons.GP(Shared.round(lootValue, 2, 2)) .. ' if sold.')
table.insert(normalTxt, '\r\n\r\nIncluding GP, the average successful pickpocket is worth ' .. Icons.GP(Shared.round(lootValue + (1 + npc.maxGP) / 2, 2, 2)) .. '.')
table.insert(sectionTxt, table.concat(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(npc.id)
table.insert(sectionTxt, rareTxt)
table.insert(sectionTxt, rareTxt)


Line 517: Line 389:
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'
areaTxt = areaTxt..'!!Price!!colspan="2"|Chance'
areaTxt = areaTxt..'!!Price!!colspan="2"|Chance'
local dropCount = Shared.tableCount(area.uniqueDrops)
local dropLines = {}
local dropLines = {}
for i, drop in pairs(area.uniqueDrops) do
for i, drop in ipairs(area.uniqueDrops) do
local thisItem = Items.getItemByID(drop.itemID)
local thisItem = Items.getItemByID(drop.id)
local lineTxt = ''
local lineTxt = ''
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..'||data-sort-value="'..drop.quantity..'"| '..Shared.formatnum(drop.quantity)..'||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.baseAreaUniqueChance/100))
lineTxt = lineTxt..'||'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
lineTxt = lineTxt..'||'..Shared.round(SkillData.Thieving.baseAreaUniqueChance, 2, 2)..'%'
dropLines[thisItem.name] = lineTxt
dropLines[thisItem.name] = lineTxt
end
end
for i, txt in Shared.skpairs(dropLines) do
for i, txt in pairs(dropLines) do
areaTxt = areaTxt..txt
areaTxt = areaTxt..txt
end
end
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..'style="text-align:right"|'..Shared.round(thievingAreaLootChance, 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.id ~= nil then
local uniqueTxt = '===Possible NPC Unique Drop==='
local thisItem = Items.getItemByID(npc.uniqueDrop.id)
uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.'
if thisItem ~= nil then
uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.'
local uniqueTxt = '===Possible NPC Unique Drop==='
local thisItem = Items.getItemByID(npc.uniqueDrop.itemID)
uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.'
uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is '
uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.'
if npc.uniqueDrop.qty > 1 then
uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is '
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.qty})
if npc.uniqueDrop.quantity > 1 then
else
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.quantity}) .. '.'
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'})
else
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'}) .. '.'
end
table.insert(sectionTxt, uniqueTxt)
end
end
table.insert(sectionTxt, uniqueTxt)
end
end


Line 561: Line 430:
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]]"
Line 573: Line 442:
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!GP!!Unique Drop'
result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!GP!!Unique Drop'
local npcArray = Shared.clone(SkillData.Thieving.NPCs)
local npcArray = GameData.getEntities(SkillData.Thieving.npcs, function(npc) return true end)
table.sort(npcArray, function(a, b) return a.level < b.level end)
table.sort(npcArray, function(a, b) return a.level < b.level end)
for i, npc in Shared.skpairs(npcArray) do
for i, npc in ipairs(npcArray) do
result = result..'\r\n|-'
result = result..'\r\n|-'
result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true})
result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true})
result = result..'||'..Icons.Icon({npc.name, type='thieving', noicon=true})
result = result..'||data-sort-value="'..npc.name..'"|'..Icons.getExpansionIcon(npc.id)..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..'||data-sort-value="' .. npc.level .. '"| '..Icons._SkillReq('Thieving', npc.level)
result = result..'||style="text-align:right"|'..npc.xp
result = result..'||style="text-align:right"|'..npc.baseExperience
result = result..'||style="text-align:right"|'..(npc.maxHit * 10)
result = result..'||style="text-align:right"|'..(npc.maxHit * 10)
result = result..'||style="text-align:right"|'..npc.perception
result = result..'||style="text-align:right" data-sort-value="' .. npc.perception .. '| '..Shared.formatnum(npc.perception)
result = result..'||data-sort-value="' .. npc.maxGP .. '"|'..Icons.GP(1, npc.maxGP)
result = result..'||data-sort-value="' .. npc.maxGP .. '"|'..Icons.GP(1, npc.maxGP)
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
if npc.uniqueDrop ~= nil then
local uniqueDrop = Items.getItemByID(npc.uniqueDrop.itemID)
local uniqueDrop = Items.getItemByID(npc.uniqueDrop.id)
if npc.uniqueDrop.qty > 1 then
if npc.uniqueDrop.quantity > 1 then
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop.qty})
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop.quantity})
else
else
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'})
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'})
Line 609: Line 478:
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)
for i, area in ipairs(SkillData.Thieving.areas) do
table.sort(areaArray, function(a, b) return a.id < b.id end)
for i, area in ipairs(areaArray) do
local minLevel, npcList, areaItemList = nil, {}, {}
local minLevel, npcList, areaItemList = nil, {}, {}
-- Build NPC list & determine level for area, this is the minimum
-- Build NPC list & determine level for area, this is the minimum
-- Thieving level required for all NPCs within that area
-- Thieving level required for all NPCs within that area
if area.npcs ~= nil and Shared.tableCount(area.npcs) > 0 then
if area.npcIDs ~= nil and not Shared.tableIsEmpty(area.npcIDs) then
for j, npcID in ipairs(area.npcs) do
for j, npcID in ipairs(area.npcIDs) do
-- Don't bother cloning the NPC below since we aren't modifying any part of it
local npc = Skills.getThievingNPCByID(npcID)
local npc = SkillData.Thieving.NPCs[npcID + 1]
if minLevel == nil or npc.level < minLevel then
if minLevel == nil or npc.level < minLevel then
minLevel = npc.level
minLevel = npc.level
Line 631: Line 497:
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
for k, drop in ipairs(area.uniqueDrops) do
for k, drop in ipairs(area.uniqueDrops) do
local areaItem = Items.getItemByID(drop.itemID)
local areaItem = Items.getItemByID(drop.id)
if areaItem == nil then
if areaItem == nil then
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]')
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]')
else
else
local iconDef = {areaItem.name, type='item'}
local iconDef = {areaItem.name, type='item'}
if drop.qty > 1 then
if drop.quantity > 1 then
iconDef.qty = drop.qty
iconDef.qty = drop.quantity
end
end
table.insert(areaItemList, Icons.Icon(iconDef))
table.insert(areaItemList, Icons.Icon(iconDef))
Line 658: Line 524:
end
end


function p.getThievingSourcesForItem(itemID)
function p._getFarmingTable(categoryName)
local resultArray = {}
local category = GameData.getEntityByName(SkillData.Farming.categories, categoryName)
if category == nil then
return 'ERROR: Invalid farming category. Please choose Allotments, Herbs, or Trees[[Category:Pages with script errors]]'
end
local seedList = GameData.getEntities(SkillData.Farming.recipes,
function(recipe)
return recipe.categoryID == category.id
end)
if Shared.tableIsEmpty(seedList) then
return ''
end


local areaNPCs = {}
local result = '{|class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
result = result..'!!XP!!Growth Time!!Seed Value'
if category.id == 'melvorD:Allotment' then
result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
elseif category.id == 'melvorD:Herb' then
result = result..'!!colspan="2"|Herb!!Herb Value'
elseif category.id == 'melvorD:Tree' then
result = result..'!!colspan="2"|Logs!!Log Value'
end
result = result..'!!Seed Sources'


--First check area unique drops
table.sort(seedList, function(a, b) return a.level < b.level end)
--If an area drops the item, add all the NPC ids to the list so we can add them later
 
if not result then
for i, seed in ipairs(seedList) do
for i, area in pairs(SkillData.Thieving.Areas) do
local seedItem = Items.getItemByID(seed.seedCost.id)
for j, drop in pairs(area.uniqueDrops) do
local productItem = Items.getItemByID(seed.productId)
if drop.itemID == itemID then
if seedItem ~= nil and productItem ~= nil then
for k, npcID in pairs(area.npcs) do
result = result..'\r\n|-'
areaNPCs[npcID] = drop.qty
result = result..'\r\n|'..Icons.Icon({seedItem.name, type='item', size='50', notext=true})
end
result = result..'|| ' .. Icons.getExpansionIcon(seedItem.id) .. Icons.Icon({seedItem.name, type='item', noicon=true})
break
result = result..'||'..seed.level..'||'..Shared.formatnum(seed.baseExperience)
end
result = result..'||data-sort-value="'..(seed.baseInterval / 1000)..'"|'..Shared.timeString(seed.baseInterval / 1000, true)
result = result..'||data-sort-value="'..seedItem.sellsFor..'"|'..Icons.GP(seedItem.sellsFor)
result = result..'||'..Icons.Icon({productItem.name, type='item', size='50', notext=true})
result = result..'|| ' .. Icons.getExpansionIcon(productItem.id) .. Icons.Icon({productItem.name, type='item', noicon=true})
if category.id == 'melvorD:Allotment' then
result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..((productItem.healsFor or 0) * 10)
end
end
result = result..'||data-sort-value="'..productItem.sellsFor..'"|'..Icons.GP(productItem.sellsFor)
result = result..'||'..ItemSourceTables._getItemSources(seedItem)
end
end
end
end


--Now go through and get drop chances on each NPC if needed
result = result..'\r\n|}'
for i, npc in pairs(SkillData.Thieving.NPCs) do
return result
local totalWt = 0
end
local dropWt = 0
 
local dropQty = 0
function p.getFarmingTable(frame)
for j, drop in pairs(npc.lootTable) do
local category = frame.args ~= nil and frame.args[1] or frame
totalWt = totalWt + drop[2]
 
if drop[1] == itemID then
return p._getFarmingTable(category)
dropWt = drop[2]
end
dropQty = drop[3]
 
end
function p.getFarmingFoodTable(frame)
end
local result = '{| class="wikitable sortable stickyHeader"'
if dropWt > 0 then
result = result..'\r\n|- class="headerRow-0"'
table.insert(resultArray, {npc = npc.name, minQty = 1, maxQty = dropQty, wt = dropWt * thievingNormalLootChance, totalWt = totalWt * 100, level = npc.level})
result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
end
result = result..'!!Healing!!Value'


--Chance of -1 on unique drops is to indicate variable chance
local recipes = GameData.getEntities(SkillData.Farming.recipes,
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID == itemID then
function(recipe)
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.qty, maxQty = npc.uniqueDrop.qty, wt = -1, totalWt = -1, level = npc.level})
local product = Items.getItemByID(recipe.productId)
end
return product ~= nil and product.healsFor ~= nil and product.healsFor > 0
end)
table.sort(recipes, function(a, b) return a.level < b.level end)


if areaNPCs[npc.id] ~= nil then
for i, recipe in ipairs(recipes) do
table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = thievingAreaLootChance, totalWt = 100, level = npc.level})
local product = Items.getItemByID(recipe.productId)
if product ~= nil and product.healsFor ~= nil and product.healsFor > 0 then
result = result..'\r\n|-'
result = result..'\r\n|'..Icons.Icon({product.name, type='item', notext='true', size='50'})
result = result..'|| ' .. Icons.getExpansionIcon(product.id) .. Icons.Icon({product.name, type='item', noicon=true})
result = result..'||style="text-align:right;"|'..recipe.level
result = result..'||style="text-align:right" data-sort-value="'..product.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(product.healsFor * 10)
result = result..'||style="text-align:right" data-sort-value="'..product.sellsFor..'"|'..Icons.GP(product.sellsFor)
end
end
end
end


for i, drop in pairs(SkillData.Thieving.RareItems) do
result = result..'\r\n|}'
if drop.itemID == itemID then
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1})
end
end


return resultArray
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 category = GameData.getEntityByName(SkillData.Farming.categories, areaName)
-- includeUnique: true|false, determines whether unique modifiers are included
if category == nil then
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated
return 'ERROR: Invalid farming category. Please choose Allotments, Herbs, or Trees[[Category:Pages with script errors]]'
-- asKeyValue: true|false, if true, returns key/value pairs like usual modifier objects
end
function p._buildAstrologyModifierArray(cons, modValue, includeStandard, includeUnique, isDistinct, asKeyValue)
local patches = GameData.getEntities(SkillData.Farming.plots,
-- Temporary function to determine if the table already contains a given modifier
function(plot)
local containsMod = function(modList, modNew)
return plot.categoryID == category.id
for i, modItem in ipairs(modList) do
end)
-- Check mod names & value data types both equal
if Shared.tableIsEmpty(patches) then
if modItem[1] == modNew[1] and type(modItem[2]) == type(modNew[2]) then
return ''
if type(modItem[2]) == 'table' then
end
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)
local result = '{|class="wikitable sortable stickyHeader"'
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
table.insert(modArray, modNew)
end
end


local modArray = {}
for i, patch in Shared.skpairs(patches) do
local isSkillMod = {}
result = result..'\r\n|-\r\n|'..i
-- Standard modifiers
result = result..'||style="text-align:right;" data-sort-value="' .. patch.level .. '"|'..patch.level
if includeStandard then
local costText = (patch.gpCost > 0 and Icons.GP(patch.gpCost)) or 'Free'
for i, skillMods in ipairs(cons.standardModifiers) do
result = result..'||style="text-align:right;" data-sort-value="'..patch.gpCost..'"|'..costText
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
-- Unique modifiers
if includeUnique then
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
result = result..'\r\n|}'
-- The most important thing we're getting here is the modText and modBase
return result
-- 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
 
if asKeyValue then
local modArrayKV = {}
for i, modDefn in ipairs(modArray) do
local modName, modVal = modDefn[1], modDefn[2]
local isSkill = isSkillMod[modName]
if modArrayKV[modName] == nil then
modArrayKV[modName] = (isSkill and { modVal } or modVal)
elseif isSkill then
table.insert(modArrayKV[modName], modVal)
else
modArrayKV[modName] = modArrayKV[modName] + modVal
end
end
return modArrayKV
else
return modArray
end
end
end


function p._buildAstrologyConstellationTable()
function p._buildAstrologyConstellationTable()
local maxModifier = 5
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 815: Line 646:
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.recipes) 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})
result = result..'||'..cons.level..'||'..cons.provides.xp
result = result..'|| ' .. Icons.getExpansionIcon(cons.id) .. name
result = result..'||'..cons.level..'||'..cons.baseExperience


local skillIconArray = {}
local skillIconArray = {}
for j, skillID in ipairs(cons.skills) do
for j, skillID in ipairs(cons.skillIDs) do
table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'}))
table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'}))
end
end
result = result..'||'..table.concat(skillIconArray, '<br/>')
result = result..'||'..table.concat(skillIconArray, '<br/>')


local standModsRaw = p._buildAstrologyModifierArray(cons, maxModifier, true, false, false, false)
--Adding a function that converts an array of connected bonuses into text [Falterfire 22/10/27]
local groupedModsToText = function(allMods)
local outArray = {}
for i, group in ipairs(allMods) do
local groupTxt = table.concat(group, ' & ')
table.insert(outArray, groupTxt)
end
return table.concat(outArray, '<br/>')
end
 
local standModsRaw = Skills._buildAstrologyModifierArray(cons, nil, true, false, false, false)
local standMods = {}
local standMods = {}
--Building the list of Standard modifiers:
--Building the list of Standard modifiers:
for j, modifier in ipairs(standModsRaw) do
for j, modifier in ipairs(standModsRaw) do
table.insert(standMods, Constants._getModifierText(modifier[1], modifier[2], false))
local modMagnitude = type(modifier[2]) == 'table' and {modifier[2]} or modifier[2]
local groupNum = modifier.group
if standMods[groupNum] == nil then standMods[groupNum] = {} end
table.insert(standMods[groupNum], Constants._getModifierText(modifier[1], modMagnitude, false))
end
end
result = result..'|| '..table.concat(standMods, '<br/>')
result = result..'|| '..groupedModsToText(standMods)


--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, nil, false, true, false, false)
local uMods = {}
local uMods = {}
for j, modifier in ipairs(uModsRaw) do
for j, modifier in ipairs(uModsRaw) do
table.insert(uMods, Constants._getModifierText(modifier[1], modifier[2], false))
local modMagnitude = type(modifier[2]) == 'table' and {modifier[2]} or modifier[2]
local groupNum = modifier.group
if uMods[groupNum] == nil then uMods[groupNum] = {} end
table.insert(uMods[groupNum], Constants._getModifierText(modifier[1], modMagnitude, false))
end
end
result = result..'||'..table.concat(uMods, '<br/>')
result = result..'|| '..groupedModsToText(uMods)
end
end
result = result..'\r\n|}'
result = result..'\r\n|}'
Line 850: Line 699:
function p.buildAstrologyConstellationTable(frame)
function p.buildAstrologyConstellationTable(frame)
return p._buildAstrologyConstellationTable()
return p._buildAstrologyConstellationTable()
end
function p.buildAstrologyValueTable()
local result = '{|class="wikitable sortable"'
result = result..'\r\n!rowspan="2"| Value!!colspan="2"| Chance'
result = result..'\r\n|-\r\n! This Value!! This Value or Greater'
local lastChance = 0
local cumulativeChance = 100
for i, chance in Shared.skpairs(SkillData.Astrology.Defaults.valueWeight) do
local thisChance = (i == 5 and chance) or chance - lastChance
result = result..'\r\n|-'
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"| ' .. cumulativeChance .. '%'
cumulativeChance = cumulativeChance - thisChance
lastChance = chance
end
result = result..'\r\n|}'
return result
end
end


return p
return p