12,787
edits
Falterfire (talk | contribs) (Formatting fixes to getSmithingTable) |
(Move Farming functions to Module:Skills/Gathering) |
||
(42 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
--This module should avoid including skill specific functions which generate | |||
--output for wiki pages, especially those which require() other modules. For | |||
--these functions, consider using the appropriate module from the below list. | |||
--Some skills have their own modules: | --Some skills have their own modules: | ||
--Module:Magic | --Module:Magic for Magic | ||
--Module:Prayer for Prayer | |||
--Module:Skills/Agility for Agility | |||
--Module:Skills/Summoning for Summoning | |||
--Module:Skills/Gathering for Mining, Fishing, Woodcutting | |||
--Module:Skills/Artisan for Smithing, Cooking, Herblore, etc. | |||
--Also be aware of: | |||
--Module:Navboxes for navigation boxes appearing near the bottom of pages | |||
local p = {} | local p = {} | ||
Line 6: | Line 18: | ||
local ItemData = mw.loadData('Module:Items/data') | local ItemData = mw.loadData('Module:Items/data') | ||
local SkillData = mw.loadData('Module:Skills/data') | local SkillData = mw.loadData('Module:Skills/data') | ||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local Constants = require('Module:Constants') | |||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
local MasteryCheckpoints = {.1, .25, .5, .95} | local MasteryCheckpoints = {.1, .25, .5, .95} | ||
function p. | -- Thieving | ||
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 | end | ||
function p. | 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 | ||
function p. | function p.getThievingSourcesForItem(itemID) | ||
local resultArray = {} | |||
local areaNPCs = {} | |||
--First check area unique drops | |||
--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, area in pairs(SkillData.Thieving.Areas) do | |||
for j, drop in pairs(area.uniqueDrops) do | |||
if drop.itemID == itemID then | |||
for k, npcID in pairs(area.npcs) do | |||
areaNPCs[npcID] = drop.qty | |||
end | |||
break | |||
end | |||
end | |||
end | |||
end | |||
--Now go through and get drop chances on each NPC if needed | |||
for i, npc in pairs(SkillData.Thieving.NPCs) do | |||
local totalWt = 0 | |||
local dropWt = 0 | |||
local dropQty = 0 | |||
for j, drop in pairs(npc.lootTable) do | |||
totalWt = totalWt + drop[2] | |||
if drop[1] == itemID then | |||
dropWt = drop[2] | |||
dropQty = drop[3] | |||
end | |||
end | |||
if dropWt > 0 then | |||
table.insert(resultArray, {npc = npc.name, minQty = 1, maxQty = dropQty, wt = dropWt * SkillData.Thieving.ItemChance, totalWt = totalWt * 100, level = npc.level}) | |||
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 = SkillData.Thieving.AreaUniqueChance, totalWt = 100, level = npc.level}) | |||
end | |||
end | |||
for i, drop in pairs(SkillData.Thieving.RareItems) do | |||
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 | |||
end | |||
-- Astrology | |||
function p.getConstellationByID(constID) | |||
return SkillData.Astrology.Constellations[constID] | |||
end | end | ||
function p. | 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 | |||
-- For a given constellation cons and modifier value modValue, generates and returns | |||
-- a table of modifiers, much like any other item/object elsewhere in the game. | |||
-- includeStandard: true|false, determines whether standard modifiers are included | |||
-- includeUnique: true|false, determines whether unique modifiers are included | |||
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated | |||
-- asKeyValue: true|false, if true, returns key/value pairs like usual modifier objects | |||
function p._buildAstrologyModifierArray(cons, modValue, includeStandard, includeUnique, isDistinct, asKeyValue) | |||
-- 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 modTypes = {} | |||
if includeStandard then | |||
table.insert(modTypes, 'standardModifiers') | |||
end | |||
if includeUnique then | |||
table.insert(modTypes, 'uniqueModifiers') | |||
end | |||
local modArray = {} | |||
local isSkillMod = {} | |||
for _, modType in ipairs(modTypes) do | |||
for i, skillMods in ipairs(cons[modType]) 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 | |||
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. | -- Mastery | ||
function p.getMasteryUnlockTable(frame) | |||
local skillName = frame.args ~= nil and frame.args[1] or frame | |||
local skillID = Constants.getSkillID(skillName) | |||
if skillID == nil then | |||
return "ERROR: Failed to find a skill ID for "..skillName | |||
end | |||
local unlockTable = SkillData.MasteryUnlocks[skillID] | |||
if unlockTable == nil then | |||
return 'ERROR: Failed to find Mastery Unlock data for '..skillName | |||
end | |||
local result = '{|class="wikitable"\r\n!Level!!Unlock' | |||
for i, unlock in Shared.skpairs(unlockTable) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|'..unlock.level..'||'..unlock.unlock | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p. | function p.getMasteryCheckpointTable(frame) | ||
local skillName = frame.args ~= nil and frame.args[1] or frame | |||
local skillID = Constants.getSkillID(skillName) | |||
if skillID == nil then | |||
return "ERROR: Failed to find a skill ID for "..skillName | |||
end | |||
if SkillData.MasteryCheckpoints[skillID] == nil then | |||
return 'ERROR: Failed to find Mastery Unlock data for '..skillName | |||
end | |||
local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses | |||
local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1] | |||
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus' | |||
for i, bonus in Shared.skpairs(bonuses) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|'..(MasteryCheckpoints[i] * 100)..'%||' | |||
result = result..Shared.formatnum(totalPoolXP * MasteryCheckpoints[i])..' xp||'..bonus | |||
end | |||
result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP' | |||
result = result..'\r\n|'..Shared.formatnum(totalPoolXP) | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p. | function p.getMasteryTokenTable() | ||
local baseTokenChance = 18500 | |||
local masterySkills = {} | |||
-- Find all mastery tokens | |||
local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end) | |||
for i, item in pairs(masteryTokens) do | |||
local milestones = SkillData.Milestones[item.skill + 1] | |||
if milestones ~= nil then | |||
table.insert(masterySkills, {tokenRef = i, skillID = item.skill, milestoneCount = milestones}) | |||
end | |||
end | |||
table.sort(masterySkills, function(a, b) | |||
if a['milestoneCount'] == b['milestoneCount'] then | |||
return a['skillID'] < b['skillID'] | |||
else | |||
return a['milestoneCount'] > b['milestoneCount'] | |||
end | |||
end) | |||
-- Generate output table | |||
local resultPart = {} | |||
local CCI = Items.getItem('Clue Chasers Insignia') | |||
local CCIIcon = Icons.Icon({'Clue Chasers Insignia', type='item', notext=true}) | |||
if CCI == nil then return '' end | |||
table.insert(resultPart, '{| class="wikitable sortable"') | |||
table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance') | |||
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon) | |||
for i, m in ipairs(masterySkills) do | |||
local token = masteryTokens[m.tokenRef] | |||
local denom = math.floor(baseTokenChance / m['milestoneCount']) | |||
local denomCCI = Shared.round(baseTokenChance / (m['milestoneCount'] * (1 + CCI.increasedItemChance / 100)), 0, 0) | |||
table.insert(resultPart, '\r\n|-') | |||
table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true})) | |||
table.insert(resultPart, '\r\n|' .. Icons.Icon({Constants.getSkillName(m['skillID']), type='skill'})) | |||
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom)) | |||
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI)) | |||
end | |||
table.insert(resultPart, '\r\n|}') | |||
return table.concat(resultPart) | |||
end | |||
-- Skill unlock costs for Adventure game mode | |||
function p.getSkillUnlockCostTable() | |||
local returnPart = {} | |||
table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost') | |||
local accCost = 0 | |||
for i, cost in ipairs(SkillData.SkillUnlockCosts) do | |||
accCost = accCost + cost | |||
table.insert(returnPart, '|-') | |||
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost)) | |||
end | |||
table.insert(returnPart, '|}') | |||
return table.concat(returnPart, '\r\n') | |||
end | end | ||
-- Accepts 1 parameter, being either: | |||
-- 'Smelting', for which a table of all bars is generated, or | |||
-- A bar or tier name, which if valid generates a table of all smithing recipes using that bar/tier | |||
function p.getSmithingTable(frame) | function p.getSmithingTable(frame) | ||
local tableType = frame.args ~= nil and frame.args[1] or frame | |||
tableType = Shared.splitString(tableType, ' ')[1] | |||
-- Translates Smithing category names to Smithing recipe data categories | |||
local categoryMap = { | |||
['Smelting'] = 0, | |||
['Bronze'] = 1, | |||
['Iron'] = 2, | |||
['Steel'] = 3, | |||
['Mithril'] = 4, | |||
['Adamant'] = 5, | |||
['Adamantite'] = 5, | |||
['Rune'] = 6, | |||
['Runite'] = 6, | |||
['Dragon'] = 7, | |||
['Dragonite'] = 7 | |||
} | |||
local categoryID = categoryMap[tableType] | |||
if categoryID == nil then | |||
return 'ERROR: Invalid Smithing category: "' .. tableType .. '"[[Category:Pages with script errors]]' | |||
end | |||
-- Build a list of recipes to be included, and a list of bars while we're at it | |||
-- The bar list will be used later for value/bar calculations | |||
local recipeList, barIDList = {}, {} | |||
for i, recipe in ipairs(SkillData.Smithing.Recipes) do | |||
if recipe.category == categoryID then | |||
local recipeItem = Items.getItemByID(recipe.itemID) | |||
if recipeItem ~= nil then | |||
table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor }) | |||
end | |||
elseif recipe.category == 0 then | |||
barIDList[recipe.itemID] = true | |||
end | |||
end | |||
-- Generate output table | |||
local resultPart = {} | |||
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\r\n|-class="headerRow-0"') | |||
table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients') | |||
--Adding value/bar for things other than smelting | |||
if categoryID > 0 then | |||
table.insert(resultPart, '!!Value/Bar') | |||
end | |||
table.sort(recipeList, function(a, b) | |||
if a.level ~= b.level then | |||
return a.level < b.level | |||
else | |||
return a.itemName < b.itemName | |||
end | |||
end) | |||
for i, recipeDef in ipairs(recipeList) do | |||
local recipe = SkillData.Smithing.Recipes[recipeDef.id] | |||
local totalValue = recipe.baseQuantity * recipeDef.itemValue | |||
-- Determine the bar quantity & build the recipe cost string | |||
local barQty, costString = 0, {} | |||
for j, itemCost in ipairs(recipe.itemCosts) do | |||
local costItem = Items.getItemByID(itemCost.id) | |||
if costItem ~= nil then | |||
table.insert(costString, Icons.Icon({costItem.name, type='item', qty=itemCost.qty, notext=true})) | |||
end | |||
if barIDList[itemCost.id] then | |||
barQty = barQty + itemCost.qty | |||
end | |||
end | |||
table.insert(resultPart, '\r\n|-') | |||
table.insert(resultPart, '\r\n| ' .. Icons.Icon({recipeDef.itemName, type='item', size=50, notext=true})) | |||
table.insert(resultPart, '\r\n| ') | |||
if recipe.baseQuantity > 1 then | |||
table.insert(resultPart, recipe.baseQuantity .. 'x ') | |||
end | |||
table.insert(resultPart, Icons.Icon({recipeDef.itemName, type='item', noicon=true})) | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.level .. '"| ' .. Icons._SkillReq('Smithing', recipe.level)) | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.baseXP .. '"| ' .. Shared.formatnum(recipe.baseXP)) | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. totalValue .. '"| ' .. Icons.GP(recipeDef.itemValue)) | |||
if recipe.baseQuantity > 1 then | |||
table.insert(resultPart, ' (x' .. recipe.baseQuantity .. ')') | |||
end | |||
table.insert(resultPart, '\r\n| ' .. table.concat(costString, ', ')) | |||
if categoryID > 0 then | |||
local barVal, barValTxt = 0, 'N/A' | |||
if barQty > 0 then | |||
barVal = totalValue / barQty | |||
barTxt = Icons.GP(Shared.round(barVal, 1, 1)) | |||
end | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barTxt) | |||
end | |||
end | |||
table.insert(resultPart, '\r\n|}') | |||
return table.concat(resultPart) | |||
end | |||
function p.getFiremakingTable(frame) | |||
local resultPart = {} | |||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\r\n|-class="headerRow-0"') | |||
table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"|'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level') | |||
table.insert(resultPart, '!!rowspan="2"|Burn Time!!colspan="2"|Without Bonfire!!colspan="2"|With Bonfire!!rowspan="2"|Bonfire Bonus!!rowspan="2"|Bonfire Time') | |||
table.insert(resultPart, '\r\n|-class="headerRow-1"') | |||
table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s') | |||
for i, logData in Shared.skpairs(SkillData.Firemaking) do | |||
local logs = Items.getItemByID(logData.logID) | |||
local name = logs.name | |||
local burnTime = logData.baseInterval / 1000 | |||
local bonfireTime = logData.baseBonfireInterval / 1000 | |||
local XPS = logData.baseXP / burnTime | |||
local XP_BF = logData.baseXP * (1 + logData.bonfireXPBonus / 100) | |||
local XPS_BF = XP_BF / burnTime | |||
table.insert(resultPart, '\r\n|-') | |||
table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true})) | |||
table.insert(resultPart, '||[['..name..']]') | |||
table.insert(resultPart, '||style ="text-align: right;"|'..logData.level) | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true)) | |||
table.insert(resultPart, '||style ="text-align: right;"|'..logData.baseXP) | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2)) | |||
table.insert(resultPart, '||style ="text-align: right;"|'..Shared.round(XP_BF, 2, 0)) | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.round(XPS_BF, 2, 2)) | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%') | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true)) | |||
end | |||
table.insert(resultPart, '\r\n|}') | |||
return table.concat(resultPart) | |||
end | end | ||
return p | return p |