Module:MoneyMakingGuide: Difference between revisions
From Melvor Idle
No edit summary |
Tag: Undo |
||
Line 21: | Line 21: | ||
local function removeNoWikiTags(str) | local function removeNoWikiTags(str) | ||
return mw.text.unstripNoWiki( | local stripped = str:gsub("^%s*<nowiki>%s*", "") | ||
:gsub("%s*</nowiki>%s*$", "") | |||
return mw.text.unstripNoWiki(stripped) | |||
end | end | ||
--- Formats a given string to TitleCase. | --- Formats a given string to TitleCase. |
Revision as of 20:48, 23 March 2024
Documentation for this module may be created at Module:MoneyMakingGuide/doc
local p = {}
local shared = require('Module:Shared')
local num = require('Module:Number')
local paramtest = require('Module:Shared/Paramtest')
local itemdb = require('Module:Items')
local icons = require('Module:Icons')
-- Constants
local MaxDynamicArgs = 20
local AmountSuffix = 'amount'
local ValueSuffix = 'value'
local SkillPrefix = 'skillExp'
-- Determines the order of Icons
local DLCParams = {
toth = icons.TotH(),
aod = icons.AoD(),
ita = icons.ItA()
}
local function removeNoWikiTags(str)
local stripped = str:gsub("^%s*<nowiki>%s*", "")
:gsub("%s*</nowiki>%s*$", "")
return mw.text.unstripNoWiki(stripped)
end
--- Formats a given string to TitleCase.
-- @param name (string) String to format.
-- @return (string) Formatted string, or the input if it's not a string.
local function formatName(name)
-- Special case to correctly format GP name.
if shared.compareString(name, 'Gold Pieces', true) or shared.compareString(name, 'GP', true) then
return 'Gold Pieces'
end
if type(name) == 'string' then
return shared.titleCase(name)
end
return name
end
local function getErrorDiv(message)
return mw.html.create('div')
:css('color', 'red')
:wikitext(message)
:done()
end
local function getItemIcon(iconName)
if iconName == 'Gold Pieces' then
return icons.GP()
end
return icons.Icon({iconName, type='item', notext=true})
end
local function getSkillExpIcon(skillName, expValue)
if expValue == nil then error("expValue is nil") end
local icon = icons.Icon({skillName, type='skill', notext=true})
expValue = num.formatnum(expValue)
return icon .. ' ' .. expValue
end
--- Parses the input and output items from the provided arguments.
-- @param args (table) Passed args table from the caller.
-- @param prefix (string) The prefix that determines input or output item parsing.
-- @return (table) A table containing parsed item information.
local function parseItemInOut(args, prefix)
local items = {}
local totalValue = 0
for i = 1, MaxDynamicArgs do
local numPrefix = prefix .. i
-- Stop parsing. Could cause problems if user input skips indexes.
if paramtest.is_empty(args[numPrefix]) then
break
end
local pName = formatName(args[numPrefix])
local pAmount = args[numPrefix .. AmountSuffix]
local pValue = args[numPrefix .. ValueSuffix]
-- Values *should* always exit this function with a non nil value.
if paramtest.has_content(pAmount) then
pAmount = tonumber(pAmount)
end
if not paramtest.has_content(pValue) or tonumber(pValue) == nil then
pValue = itemdb.getItemValue(pName)
else
pValue = tonumber(pValue)
end
-- Default to 0 to add to totals.
local totVal = pValue or 0
local totAmt = pAmount or 0
totalValue = totalValue + (totVal * totAmt)
table.insert(items, {
prmNumber = i,
name = pName,
amount = pAmount,
value = pValue})
end
return {
TotalValue = totalValue,
Items = items
}
end
--- Parses the skill experience from the provided arguments.
-- @param args (table) Passed args table from the caller.
-- @return (table) A table containing parsed skill experience information.
local function parseExp(args)
local skills = {}
for i = 1, MaxDynamicArgs do
local skillPrefix = 'skillExp' .. i
-- Stop parsing. Could cause problems if user input skips indexes.
if paramtest.is_empty(args[skillPrefix]) then
break
end
local pSkill = formatName(args[skillPrefix])
local pExp = args[skillPrefix .. AmountSuffix]
if paramtest.has_content(pExp) then
pExp = tonumber(pExp)
end
table.insert(skills, {
prmNumber = i,
name = pSkill,
exp = pExp})
end
return skills
end
--- Builds the section of the mmg table that shows the input and output items.
-- @param items (table) A table containing items
-- @return (string) The HTML representation of the item table section.
local function buildItemTable(itemSet)
if itemSet == nil or itemSet.Items == nil or #itemSet.Items == 0 then
return nil
end
-- Create table with header for items
local tbl = mw.html.create('table')
tbl:addClass('wikitable sortable text-align-center')
:attr('style', 'font-size: 1em; width: calc(100% + 2px); margin: -1px')
:tag('tr')
:tag('th')
:addClass('unsortable')
:tag('th'):wikitext('Item')
:tag('th'):wikitext('Quantity')
:tag('th'):wikitext('Value')
:done()
-- Create individual rows for items
for _, i in pairs(itemSet.Items) do
local row = mw.html.create('tr')
local imgCell = mw.html.create('td')
:wikitext(getItemIcon(i.name))
:done()
local itemCell = mw.html.create('td')
:css('text-align','left')
:wikitext(string.format('[[%s]]', i.name))
:done()
local qtyCell = mw.html.create('td')
if i.amount == nil then
qtyCell:node(getErrorDiv("Unable to parse quantity for item: " .. i.name))
else
local qty = num.round2(i.amount)
qtyCell:wikitext(num.formatnum(qty))
end
qtyCell:done()
local valCell = mw.html.create('td')
if i.value == nil then
valCell:node(getErrorDiv("Unable to get value for item: " .. i.name))
else
local tot = i.value * (i.amount or 0)
valCell:wikitext(icons.GP(num.round2(tot)))
end
valCell:done()
-- Add all cells to row, and row to table.
row:node(imgCell):node(itemCell):node(qtyCell):node(valCell):done()
tbl:node(row)
end
return tbl:done()
end
--- Builds the section of the mmg table that shows the skill experience gained.
-- @param items (table) A table containing skills and experience values.
-- @return (string) The HTML representation of the item table section.
local function buildExpLayout(skills)
local layoutLines = {}
for _, skillLine in pairs(skills) do
local hasInvalidExp = false
local span = nil
if skillLine.exp == nil or skillLine.exp <= 0 then
hasInvalidExp = true
end
local skillExp = num.round2(skillLine.exp or 0)
local skillIcon = getSkillExpIcon(skillLine.name, skillExp)
if hasInvalidExp then
span = getErrorDiv("Skill in parameter " .. skillLine.prmNumber .. " has an invalid experience value.")
else
span = mw.html.create('div')
:wikitext(skillIcon)
:done()
end
table.insert(layoutLines, span)
end
return layoutLines
end
function p._buildMMGTable(args)
if args['guideName'] == nil then
error("Money Making Guide must have a valid guideName parameter.")
end
-- Parse arguments.
local pSkills = parseExp(args)
local pInputs = parseItemInOut(args, 'input')
local pOutputs = parseItemInOut(args, 'output')
local dlcIcons = p.getDLCIcons(args['dlc'], ' ')
local explanation = removeNoWikiTags(args['explanation'] or '')
local tbl = mw.html.create()
local html = tbl:tag("table")
:addClass("wikitable")
:attr('style', 'width: 100%; text-align: center;')
:tag("tr")
:tag("td")
:attr("colspan", 2)
:wikitext(table.concat(dlcIcons) .. ' ')
:wikitext(args['guideName'] or '{{{guideName}}}')
:tag("tr")
:tag("th")
:attr("colspan", 2)
:wikitext("Requirements")
:tag('tr')
:tag('th')
:wikitext('Skills')
:tag('th')
:wikitext('Other')
:done()
:tag("tr")
:tag("td")
:wikitext(paramtest.default_to(args['skills'], 'None'))
:tag("td")
:wikitext(paramtest.default_to(args['other'], 'None'))
:tag('tr')
:tag('th')
:wikitext('Items')
:tag('th')
:wikitext('Recommended')
:tag('tr')
:tag('td')
:wikitext(paramtest.default_to(args['items'], 'None'))
:tag('td')
:wikitext(paramtest.default_to(args['recommended'], 'None'))
:tag("tr")
:tag("th")
:attr("colspan", 2)
:wikitext("Results")
:tag("tr")
:tag("th")
:wikitext("Profit")
:tag("th")
:wikitext("Experience gained")
:tag("tr")
:tag("td")
if args['profit'] then
html:wikitext(icons.GP(num.round2(args['profit'])))
else
html:wikitext(icons.GP(num.round2(pOutputs.TotalValue - pInputs.TotalValue)))
end
html = html
:done()
:tag("td")
if pSkills ~= nil then
for _, v in ipairs(buildExpLayout(pSkills)) do
html:node(v)
end
else
html:wikitext("None")
end
html = html
:tag("tr")
:tag("th")
:wikitext("Inputs")
if pInputs.TotalValue ~= 0 then
html:wikitext(" (" .. icons.GP(num.round2(pInputs.TotalValue)) .. ")")
end
html = html
:tag("th")
:wikitext("Outputs")
if pOutputs.TotalValue ~= 0 then
html:wikitext(" (" .. icons.GP(num.round2(pOutputs.TotalValue)) .. ")")
end
html = html
:tag("tr")
:tag("td")
local inputsTable = buildItemTable(pInputs)
if inputsTable ~= nil then
html:css('padding', '0')
:css('vertical-align', 'top')
:node(inputsTable)
else
html:wikitext("None")
end
html = html
:tag("td")
local outputsTable = buildItemTable(pOutputs)
if outputsTable ~= nil then
html:css('padding', '0')
:css('vertical-align', 'top')
:node(outputsTable)
else
html:wikitext("None")
end
html = html
:done()
:done()
return tbl:done()
:newline()
:wikitext(explanation)
end
function p.buildMMGTable(frame)
local args = frame:getParent().args
return p._buildMMGTable(args)
end
--- Returns the parsed result of the dlcParam.
-- @param dlcParam (string) A string separated by , listing the DLCs required for the MMG
-- @return (table) A table containing the items of provided DLCs
function p.getDLCIcons(dlcParam)
if type(dlcParam) ~= 'string' then return {} end
local input = shared.splitString(dlcParam, ',', true)
local dlcs = {}
for _, arg in pairs(input) do
local argLower = arg:lower()
table.insert(dlcs, DLCParams[argLower])
end
return dlcs
end
function p.main(frame)
error("Call a specific function rather than main.")
end
function p.test()
local args = {
guideName = 'My guide',
interval = nil,
skills = [=[
{{SkillReq|Woodcutting|12}}</br>
{{SkillReq|Mining|40}}]=],
items =[=[
{{ItemIcon|Diamond}} At least 3 diamonds]=],
other =
[=[{{ZoneIcon|Into the Mist}} completion]=],
recommended =[=[
{{ItemIcon|Thieving Skillcape}}</br>
[[Thieving#Mastery Pool Checkpoints|95% Thieving Mastery Pool Checkpoint]]]=],
skillExp1 ='mining',
skillExp1amount =-50000,
skillExp2 ='woodcutting',
skillExp2amount =123456,
input1 ='magic logs',
input1amount =500,
input2 ='nature rune',
input2amount =500.13,
input2value =69,
output1 ='gp',
output1amount =250000,
category ="Non-combat",
dlc = "aod, toth",
intensity = nil,
explanation =[=[
<nowiki>
===== Woodcutting =====
{| class="wikitable"
! Name
! Effect
|-
| {{UpgradeIcon|Dragon Axe}} || -40% cut time
|-
| {{ItemIcon|Woodcutting Skillcape}} || -15% Base Woodcutting Interval (additive with the bonuses above a total of -55% cut time)
|-
| {{UpgradeIcon|Master of Nature}} || -15% Base Woodcutting Interval (additive with the bonuses above to a total of -70% cut time)
|-
| {{MasteryReq|Redwood Logs|99}}<br> {{MasteryReq|Yew Logs|99}} || Decreased cut interval by 0.2s for this Tree (on top of the -70% cut time from above modifiers)<br> +45% chance to receive 2x Logs per action
|-
| {{ItemIcon|Bird Nest Potion IV}} || +30% Chance for [[Bird Nest]]s to drop in Woodcutting
|-
| {{ItemIcon|Lumberjack's Top}} || +2% Bird Nest drop rate
|-
| [[Woodcutting#Mastery Pool Checkpoints|25% Woodcutting Mastery Pool Checkpoint]] || +5% increased chance to receive double Logs per action
|-
| [[Woodcutting#Mastery Pool Checkpoints|50% Woodcutting Mastery Pool Checkpoint]] || All Logs sell for +50% GP Value
|-
| [[Woodcutting#Mastery Pool Checkpoints|95% Woodcutting Mastery Pool Checkpoint]] || When you receive a Birds Nest, always receive a base minimum of 2.
|-
| {{PetIcon|Beavis}} || +5% Chance to Double Items in Woodcutting
|-
| {{UpgradeIcon|Multi-Tree}} || cut 2 different trees simultaneously
|-
| {{MasteryReq|Deedree|type=constellation|99}} || +15% Chance to Double Items In Woodcutting and +15% Chance for Bird Nests to drop in Woodcutting
|}
====Ratios====
For each hour of Fletching you'll need to spend (on average):
* 00:01s mining {{ItemIcon|Rune Essence}}
* 00:11.5s mining {{ItemIcon|Dragonite Ore}}
* 00:23s mining {{ItemIcon|Runite Ore}}
* 00:27s runecrafting {{ItemIcon|Spirit Rune}}
* 01:04s runecrafting {{ItemIcon|Lava Rune}}
* 01:21s Superheat IV'ing {{ItemIcon|Dragonite Bar}}
* 37:16s Woodcutting {{ItemIcon|Redwood Logs}} and {{ItemIcon|Yew Logs}}
* 09:38s Smithing {{ItemIcon|Dragon Javelin Heads}}
</nowiki>]=],
}
mw.log(p._buildMMGTable(args))
end
return p