Documentation for this module may be created at Module:Skill calc/doc
--[=[
dependencies
[[Module:Addcommas]]
[[Module:Tables]]
[[Module:Coins]]
[[Module:GETotal]]
[[Module:Number]]
[[Module:Experience]]
[[Module:Silverhawks/data]]
[[Module:Skill_calc/eltGenerator]]
[[Module:Skill_calc/bonusGenerator]]
TODO
Add support for other currencies
Get input on bonus stacking
--]=]
-- <nowiki>
local p = {}
local commas = require('Module:Addcommas')._add
local tables = require('Module:Tables')
local coins = require('Module:Coins')._amount
local gePrice = require('Module:GETotal')._quantity
local numbers = require('Module:Number')._round
local level = require('Module:Experience').level_at_xp
local xp = require('Module:Experience').xp_at_level
local featherExp = require('Module:Silverhawks/data')
-- This houses most of the processing power
local eltGenerator = require('Module:Skill_calc/eltGenerator')
local bonusGenerator = require('Module:Skill_calc/bonusGenerator')
function p.noValue(frame)
local args = frame:getParent().args
local pctExpBoost = 0 -- Account for outfits, avatar, tools, etc
local flatExpBoost = 0 -- Account for flat experience boosts
local currLv, goalLv, currXP, goalXP, remaining
local icon,links,elts
-- These sub-sections have different table elts than their parent skill
local exceptions =
{"Flatpacks", "Forging", "Multiples", "Rooms", "Milestones", "Agility-Other",
"Fishing-Dungeoneering", "Woodcutting-Other", "Tiaras", "Runespan - Free",
"Runespan - Members", "Conversion"}
local bonusExceptions =
{"Urns", "Dungeoneering", "Blast furnace"}
-- These skills have no special considerations in Dungeoneering
local basicDungeons =
{"Mining", "Woodcutting"}
-- Gather all relative experience boosts to find new base experience
if not (args.avatar == nil) and not (args.disp == "Urns") then
pctExpBoost = pctExpBoost + bonusGenerator(args.skill, "avatar", tonumber(args.avatar))
end
if not (args.elite == nil) and not (args.disp == "Urns") then
pctExpBoost = pctExpBoost + bonusGenerator(args.skill, "elite", tonumber(args.elite))
end
-- Some bonuses can not be used on some sub-sections (E.G. Dungeoneering)
if not findItem(bonusExceptions, args.disp) then
if not (args.abyss == nil) then
pctExpBoost =
pctExpBoost + bonusGenerator(args.skill, "abyss", args.abyss)
end
if not (args.extra == nil) and args.abyss == nil then
if args.skill == "Cooking" then
pctExpBoost = pctExpBoost + bonusGenerator(args.disp, "extra", args.extra)
else
pctExpBoost = pctExpBoost + bonusGenerator(args.skill, "extra", args.extra)
end
end
if not (args.outfit == nil) then
pctExpBoost = pctExpBoost + bonusGenerator(args.skill, "outfit", tonumber(args.outfit))
end
if not (args.portable == nil) and (args.portable == "Yes") then
pctExpBoost = pctExpBoost + bonusGenerator(args.skill, "portable", 1)
end
if not (args.tool == nil) then
flatExpBoost = flatExpBoost + bonusGenerator(args.skill, "tool", args.tool)
end
end
-- Translate goals into experience comparisons
-- Calculate iterations to goal
currLv, currXP, goalLv, goalXP, remaining = remainingExp(args.current, args.goal, args.currToggle, args.goalToggle)
-- Try to catch Silverhawk feathers early..
if args.disp == "Silverhawk Feathers" then
local message = silverhawkFeathers(currLv, goalLv, currXP, goalXP, remaining, pctExpBoost)
local msgRet = mw.html.create('div'):css({['font-size'] = "16px", ['font-weight'] = "bold"}):wikitext(message)
return tostring(msgRet)
end
-- Grab Sub-Category Table Data
local dataRet = require('Module:Skill calc/' .. args.skill .. '/data')
local data = dataRet(args.disp)
table.sort(data, function(a,b) return sortTable(a,b) end )
local ret = mw.html.create('table'):addClass('wikitable sortable')
-- Find columns from pool
-- Some require specific parameters due to common phrases
local eltsRet = require('Module:Skill calc/elts')
elts = eltsRet[args.skill]
-- Filter out exceptions
if findItem(exceptions, args.disp) or findItem(exceptions, args.skill .. '-' .. args.disp) then
elts = eltsRet[args.skill .. "-" .. args.disp]
-- All urns share the same format; special exception
elseif args.disp == "Urns" then
elts = eltsRet[args.disp]
-- Basic dungeon format
elseif args.disp == "Dungeoneering" then
elts = eltsRet["Dungeons"]
end
tables._row(ret:tag('tr'), elts, true)
for _, v in ipairs(data) do
--Leave common calculations outside of the function calls
--Total materials to create items
local mcount = 1
if v.mcount then
mcount = v.mcount
end
--Get total cost of materials
local cost = 0
if (v.material) then
if (args.skill ~= "Fishing") and (v.trade ~= 0) and (v.mtrade ~= 0) then
cost = gePrice(v.material, mcount)
end
-- Make an exception for Fishing and urns
if ((args.skill == "Fishing" or args.skill == "Divination") and (v.mtrade ~= 0)) or args.disp == "Urns" then
cost = gePrice(v.material, mcount)
end
end
local productCost = 0
if (args.skill == "Smithing" or
args.skill == "Crafting" or
args.skill == "Fishing" or
args.skill == "Cooking") and v.trade ~= 0 then
productCost = gePrice({1,v.name},1)
elseif (args.skill == "Woodcutting" or
args.skill == "Mining" or
args.skill == "Runecrafting" or
args.skill == "Divination") and v.trade ~= 0 then
if (v.icon == nil) then
productCost = gePrice({1, v.name}, 1)
else
productCost = gePrice({1, v.icon}, 1)
end
end
-- Check for other currencies
if v.currency then
productCost = v.value end
--Establish any experience boosts
local abyss = false
if not (args.abyss == nil) and
(args.abyss == "Yes" or args.abyss == "Demonic Skull") then
abyss = true end
local unitExp = calculateBonus
{
base = v.xp,
boost = pctExpBoost,
flatBoost = flatExpBoost,
abyss = abyss
}
-- Calculate needed iterations
local needed
if unitExp ~= 0 then
needed = tonumber(math.ceil(remaining / unitExp))
else
needed = 0
end
-- Decide Label
local fileName = v.name
if v.page then fileName = v.page end
if v.alt then fileName = v.alt end
-- Icon extension
local ext = ".png"
if v.ext then ext = v.ext end
-- File name of Icon
local icon = v.icon
-- No Profit, No Loss skills
if (args.skill == "Agility"
or args.skill == "Thieving"
or args.skill == "Slayer") then
elts = eltGenerator.generate_NoProfitNoLoss({args = {v,unitExp,needed,ext,icon,fileName,args,remaining}})
-- No Loss, Profit skills (Gathering)
elseif (args.skill == "Mining"
or args.skill == "Fishing"
or args.skill == "Woodcutting"
or args.skill == "Runecrafting"
or args.skill == "Divination") then
elts = eltGenerator.generate_ProfitNoLoss({args = {v,unitExp,needed,ext,icon,fileName, productCost, cost, args, remaining}})
-- No Profit, Loss skills (Survival)
elseif (args.skill == "Firemaking"
or args.skill == "Prayer"
or args.skill == "Construction") then
elts = eltGenerator.generate_NoProfitLoss({args = {v,unitExp,needed,fileName,cost,args}})
-- Profit and Loss skills (Artisan)
-- Fletching, Cooking, Farming, Smithing, Herblore, Summoning
else
elts = eltGenerator.generate_ProfitLoss({args = {v,unitExp,cost,productCost,needed,fileName,args}})
end
if args.skill == "Thieving" then table.insert(elts,v.location) end
-- Allow for items with no level requirement
local levelRequired = 1
if v.level then levelRequired = v.level end
local class = 'sg-yellow'
if levelRequired > goalLv then
class = 'sg-red'
elseif levelRequired <= currLv then
class = 'sg-green'
end
tables._row(ret:tag('tr'):addClass(class), elts, false)
end
local message = displayExp{display=args.disp, skill=args.skill, remaining=remaining, goalLv=goalLv, goalXP=goalXP, currLv = currLv, currXP = currXP}
return tostring(message) .. tostring(ret)
end
--[=[ displayExp
-- Creates a text string output for the goal calculations
-- Inputs:
-- params Incoming parameters to generate string
-- - display Current set of sub-categorical data
-- - skill Current skill
-- - remaining Experience needed for goal
-- - goalXP Expected experience
-- - goalLv Expected level
-- - currXP Current experience
-- - currLv Current level
-- Returns:
-- message String created from params
-- - this may be appended with a warning specifically for flatpacks
--]=]
function displayExp(params)
local message
local display = params.display
local skill = params.skill
local remaining = params.remaining
local goalXP = params.goalXP
local goalLv = params.goalLv
local currXP = params.currXP
local currLv = params.currLv
message = "To train " .. skill .. " from " .. commas(currXP) .. " experience (level " .. currLv .. ") to " .. commas(goalXP) .. " experience (level " .. goalLv .. "), " .. commas(remaining) .. " experience is required."
if display == "Flatpacks" then
message = message .. "<div style='color:red; font-size:12px;'>Levels refer to the minimum needed to use the associated workbench if otherwise lower.</div>"
end
local ret = mw.html.create('div'):css({['font-size'] = "16px", ['font-weight'] = "bold"}):wikitext(message)
return tostring(ret)
end
--[=[ remainingExp
-- Finds and returns experiences and levels based on inputs
-- Inputs:
-- curr current value
-- goal goal value
-- curr_intent what the current is (level/experience)
-- goal_intent what the goal is (level/experience)
-- Returns:
-- current level,
-- current experience,
-- goal level,
-- goal experience,
-- experience remaining
--]=]
function remainingExp(curr, goal, curr_intent, goal_intent)
local goalLevel, currLevel, goalXP, currXP
if curr_intent == "Level" and tonumber(curr) <= 120 then
currLevel = tonumber(curr)
currXP = xp({args = {curr}})
else
currLevel = level({args = {curr}})
currXP = tonumber(curr)
end
if goal_intent == "Level" and tonumber(goal) <= 120 then
goalLevel = tonumber(goal)
goalXP = xp({args = {goal}})
else
goalLevel = level({args = {goal}})
goalXP = tonumber(goal)
end
-- Prevent negative values
local remaining = math.ceil(goalXP - currXP)
if remaining < 0 then
remaining = 0
end
return currLevel, currXP, goalLevel, goalXP, remaining
end
--[=[ calculateBonus
-- Inputs:
-- source Incoming data
-- - base Base experience for item
-- - boost Percent experience boost, expressed as a decimal percentage
-- - ava Avatar bonus
-- - outfit Outfit bonus
-- - tools Extra bonuses
-- - flatBoost Flat experience boost
-- Returns:
-- Numeric value of new base experience including bonuses
--]=]
function calculateBonus(source)
local total = source.base * (1 + source.boost)
-- If the abyss is active, do not add 1
-- Not sure how it all stacks :: NEED INPUT
if source.abyss == true then total = source.base * source.boost end
total = total + source.flatBoost
return numbers(total,1)
end
--[=[ silverhawkFeathers
-- Inputs:
-- currlv Current level
-- goalLv Goal Level
-- currXP Current Experience
-- goalXP Goal Experience
-- remaining Difference from goal
-- pctExpBoost Calculated boost to base exp
-- Returns:
-- String including needed feathers, remaining experience, and current GE cost for feathers
--]=]
function silverhawkFeathers(currLv, goalLv, currXP, goalXP, remaining, pctExpBoost)
local feathers = 0
local featherXP
local desc = ""
local workingXP = currXP
local workingLv = currLv
while workingXP < goalXP do
feathers = feathers + 1
workingLv = level({args = {workingXP}})
featherXP = featherExp[workingLv] + (featherExp[workingLv] * pctExpBoost)
workingXP = workingXP + featherXP
end
return 'To train from ' .. commas(currXP) .. ' experience (level ' .. currLv .. ') to ' .. commas(goalXP) .. ' experience (level ' .. goalLv .. '), ' .. commas(remaining) .. " experience is needed. This requires '''" .. commas(feathers) .. " [[Silverhawk feathers|feathers]]''' and, at current GE Prices, will cost " .. coins(gePrice({feathers,'Silverhawk feathers'},1)) .. ' coins.'
end
-- Make it easier to find items in a set
-- A little heavy : if someone can find a better way, replace.
function findItem (list, item)
local status = false
for _,v in pairs(list) do
if v == item then
status = true
break
end
end
return status
end
function sortTable(a, b)
local value = false
if a.name and b.name and
a.name == b.name and
(a.title or b.title) then
if a.title and not b.title then
value = a.title < b.name
elseif not a.title and b.title then
value = a.name < b.title
else
value = a.title < b.title end
elseif a.level and b.level then
value = a.level < b.level
else
value = a.xp < b.xp
end
return value
end
return p