Module:MoneyMakingGuide: Difference between revisions
From Melvor Idle
mNo edit summary |
(Fix currency function calls) |
||
(32 intermediate revisions by one other user not shown) | |||
Line 6: | Line 6: | ||
local itemdb = require('Module:Items') | local itemdb = require('Module:Items') | ||
local icons = require('Module:Icons') | local icons = require('Module:Icons') | ||
local StringBuilder = require('Module:StringBuilder') | |||
-- Constants | -- Constants | ||
Line 19: | Line 20: | ||
ita = icons.ItA() | ita = icons.ItA() | ||
} | } | ||
local SpecialItems = { | |||
['GP'] = 'Gold Pieces', | |||
['SC'] = 'Slayer Coins', | |||
} | |||
local SpecialIcons = { | |||
['Gold Pieces'] = icons._Currency('melvorD:GP'), | |||
['Slayer Coins'] = icons._Currency('melvorD:SlayerCoins'), | |||
} | |||
--- Possible fix to allow wikicode (especially tables) to be passed to the 'explanation' | |||
--- parameter, and get parsed by the wiki parser on return. | |||
-- Not used currently, because it might not be necessary at all. | |||
local function processMarkup(str) | |||
--return frame:preprocess(str) | |||
end | |||
--- Formats a given string to TitleCase. | --- Formats a given string to TitleCase. | ||
-- @param name (string) String to format. | -- @param name (string) String to format. | ||
-- @return (string) Formatted string, or the input if it's not a string. | -- @return (string) Formatted string, or the input if it's not a string. | ||
local function | local function formatItemName(name) | ||
-- Special case to correctly format GP name. | -- Special case to correctly format GP name. | ||
if | if SpecialItems[name] then | ||
return SpecialItems[name] | |||
end | end | ||
-- Got to solve too many edge case swith this (like of and IV) | |||
-- Better just make the user use the correct name instead. | |||
--if type(name) == 'string' then | |||
-- return shared.titleCase(name) | |||
--end | |||
return name | |||
end | |||
local function formatSkillName(name) | |||
if type(name) == 'string' then | if type(name) == 'string' then | ||
return shared.titleCase(name) | return shared.titleCase(name) | ||
Line 44: | Line 72: | ||
local function getItemIcon(iconName) | local function getItemIcon(iconName) | ||
if iconName | if SpecialIcons[iconName] then | ||
return | return SpecialIcons[iconName] | ||
end | end | ||
return icons.Icon({iconName, type='item', notext=true}) | return icons.Icon({iconName, type='item', notext=true}) | ||
end | end | ||
Line 58: | Line 86: | ||
return icon .. ' ' .. expValue | return icon .. ' ' .. expValue | ||
end | |||
local function parseProfitMargin(minProfit, maxProfit) | |||
-- Manually set maxProfit to numeric 0, only if it's nil. | |||
if maxProfit == nil then maxProfit = 0 end | |||
local min = tonumber(minProfit) | |||
local max = tonumber(maxProfit) | |||
if max == nil then | |||
error("maxProfit is not a valid number.") | |||
end | |||
local sb = StringBuilder:new() | |||
if min then | |||
sb :append(icons._Currency('melvorD:GP', num.round2(min))) | |||
:append(' to ') | |||
end | |||
sb :append(icons._Currency('melvorD:GP', num.round2(max))) | |||
:append(' per hour') | |||
return sb:toString() | |||
end | end | ||
Line 76: | Line 127: | ||
end | end | ||
local pName = | local pName = formatItemName(args[numPrefix]) | ||
local pAmount = args[numPrefix .. AmountSuffix] | local pAmount = args[numPrefix .. AmountSuffix] | ||
local pValue = args[numPrefix .. ValueSuffix] | local pValue = args[numPrefix .. ValueSuffix] | ||
Line 122: | Line 173: | ||
end | end | ||
local pSkill = | local pSkill = formatSkillName(args[skillPrefix]) | ||
local pExp = args[skillPrefix .. AmountSuffix] | local pExp = tonumber(args[skillPrefix .. AmountSuffix]) | ||
table.insert(skills, { | table.insert(skills, { | ||
prmNumber = i, | prmNumber = i, | ||
Line 135: | Line 182: | ||
end | end | ||
-- Return nil if there is no experience earned. | |||
-- Lets the parent table know to enter 'None' instead | |||
if #skills == 0 then | |||
return nil | |||
end | |||
return skills | return skills | ||
end | end | ||
Line 164: | Line 216: | ||
local imgCell = mw.html.create('td') | local imgCell = mw.html.create('td') | ||
:wikitext(getItemIcon(i.name)) | :wikitext(getItemIcon(i.name)) | ||
:css('height', '28px') | |||
:done() | :done() | ||
Line 175: | Line 228: | ||
qtyCell:node(getErrorDiv("Unable to parse quantity for item: " .. i.name)) | qtyCell:node(getErrorDiv("Unable to parse quantity for item: " .. i.name)) | ||
else | else | ||
local qty = num. | local qty = num.autoround(i.amount) | ||
qtyCell:wikitext(num.formatnum(qty)) | qtyCell:wikitext(num.formatnum(qty)) | ||
end | end | ||
Line 185: | Line 238: | ||
else | else | ||
local tot = i.value * (i.amount or 0) | local tot = i.value * (i.amount or 0) | ||
valCell:wikitext(icons.GP | valCell | ||
:css('text-align','right') | |||
:wikitext(icons._Currency('melvorD:GP', num.round2(tot))) | |||
:attr("data-sort-value", tot) | |||
end | end | ||
valCell:done() | valCell:done() | ||
Line 227: | Line 283: | ||
end | end | ||
function p._buildMMGTable(args) | |||
if args['guideName'] == nil then | |||
error("Money Making Guide must have a valid guideName parameter.") | |||
end | |||
-- Parse arguments. | -- Parse arguments. | ||
local pSkills = parseExp(args) | local pSkills = parseExp(args) | ||
Line 233: | Line 293: | ||
local pOutputs = parseItemInOut(args, 'output') | local pOutputs = parseItemInOut(args, 'output') | ||
local dlcIcons = p.getDLCIcons(args['dlc'], ' ') | local dlcIcons = p.getDLCIcons(args['dlc'], ' ') | ||
local minProfit = args['minimumProfit'] | |||
local maxProfit = pOutputs.TotalValue - pInputs.TotalValue | |||
local tbl = mw.html.create() | local tbl = mw.html.create() | ||
Line 242: | Line 304: | ||
:tag("td") | :tag("td") | ||
:attr("colspan", 2) | :attr("colspan", 2) | ||
:css('font-weight', 'bold') | |||
:wikitext(table.concat(dlcIcons) .. ' ') | :wikitext(table.concat(dlcIcons) .. ' ') | ||
:wikitext(args['guideName'] or '{{{guideName}}}') | :wikitext(args['guideName'] or '{{{guideName}}}') | ||
:tag("tr") | |||
:tag("td") | |||
:attr("colspan", 2) | |||
:wikitext(parseProfitMargin(minProfit, maxProfit)) | |||
:tag("tr") | :tag("tr") | ||
:tag("th") | :tag("th") | ||
Line 256: | Line 323: | ||
:tag("tr") | :tag("tr") | ||
:tag("td") | :tag("td") | ||
:addClass('mmg-no-list') | |||
:newline() | |||
:wikitext(paramtest.default_to(args['skills'], 'None')) | |||
:tag("td") | :tag("td") | ||
:addClass('mmg-no-list') | |||
:newline() | |||
:wikitext(paramtest.default_to(args['other'], 'None')) | |||
:tag('tr') | :tag('tr') | ||
:tag('th') | :tag('th') | ||
Line 266: | Line 337: | ||
:tag('tr') | :tag('tr') | ||
:tag('td') | :tag('td') | ||
:addClass('mmg-no-list') | |||
:newline() | |||
:wikitext(paramtest.default_to(args['items'], 'None')) | :wikitext(paramtest.default_to(args['items'], 'None')) | ||
:tag('td') | :tag('td') | ||
:addClass('mmg-no-list') | |||
:newline() | |||
:wikitext(paramtest.default_to(args['recommended'], 'None')) | :wikitext(paramtest.default_to(args['recommended'], 'None')) | ||
:tag("tr") | :tag("tr") | ||
Line 281: | Line 356: | ||
:tag("td") | :tag("td") | ||
if args['profit'] then | if args['profit'] then | ||
html:wikitext(icons.GP | html:wikitext(icons._Currency('melvorD:GP', num.round2(args['profit']))) | ||
else | else | ||
html:wikitext(icons.GP | html:wikitext(icons._Currency('melvorD:GP', num.round2(maxProfit))) | ||
end | end | ||
html = html | html = html | ||
Line 300: | Line 375: | ||
:wikitext("Inputs") | :wikitext("Inputs") | ||
if pInputs.TotalValue ~= 0 then | if pInputs.TotalValue ~= 0 then | ||
html:wikitext(" (" .. icons.GP | html:wikitext(" (" .. icons._Currency('melvorD:GP', num.round2(pInputs.TotalValue)) .. ")") | ||
end | end | ||
html = html | html = html | ||
Line 306: | Line 381: | ||
:wikitext("Outputs") | :wikitext("Outputs") | ||
if pOutputs.TotalValue ~= 0 then | if pOutputs.TotalValue ~= 0 then | ||
html:wikitext(" (" .. icons.GP | html:wikitext(" (" .. icons._Currency('melvorD:GP', num.round2(pOutputs.TotalValue)) .. ")") | ||
end | end | ||
html = html | html = html | ||
Line 333: | Line 408: | ||
:done() | :done() | ||
return | return tbl:done() | ||
:newline() | |||
end | |||
function p.buildMMGTable(frame) | |||
local args = frame:getParent().args | |||
return p._buildMMGTable(args) | |||
end | end | ||
Line 353: | Line 434: | ||
function p.main(frame) | function p.main(frame) | ||
error("Call a specific function rather than main.") | |||
end | end | ||
function p.test() | function p.test() | ||
local args = { | local args = { | ||
guideName = | guideName ='Mining Pure Crystal', | ||
category ='Non-combat', | |||
dlc ='aod, toth', | |||
skills =[[ | |||
*{{SkillReq|Mining|85}} | |||
*{{SkillReq|Herblore|53}}]], | |||
items =[=[ | |||
*{{ItemIcon|Mining Gloves}} | |||
*{{ItemIcon|Perfect Swing Potion IV}}]=], | |||
other =[=[ | |||
*{{ItemIcon|Pure Crystal|notext=true}}{{Icon|Mastery|notext=true}} Level 99 | |||
*{{UpgradeIcon|Dragon Pickaxe}} | |||
*[[Mining#Mastery Pool Checkpoints|95% Mining Mastery Pool Checkpoint]]]=], | |||
recommended =[=[ | |||
[[Money_Making/Mining_Pure_Crystal#Improves_GP_Rate|Bonusses that improve profit]]]=], | |||
} | } | ||
local t = p._buildMMGTable(args) | |||
mw.log(t) | |||
end | end | ||
-- function p.test() | |||
-- local args = { | |||
-- guideName = "", | |||
-- interval = "", | |||
-- skills = "", | |||
-- items = "", | |||
-- other = "", | |||
-- recommended = "", | |||
-- skillExp1 = "Maagic", | |||
-- skillExp1amount = "-1000", | |||
-- skillExp2 = "Mining", | |||
-- skillExp2amount = "420", | |||
-- input1 = "nature rune", | |||
-- input1amount = "5", | |||
-- input1value = "69", | |||
-- input2 = "Fire Rune", | |||
-- input2amount = "20", | |||
-- output1 = "gp", | |||
-- output1amount = "1050", | |||
-- category = "", | |||
-- dlc = "aod, toth", | |||
-- intensity = "", | |||
-- explanation = "" | |||
-- } | |||
-- | |||
-- mw.log(p._buildMMGTable(args)) | |||
-- end | |||
return p | return p |
Latest revision as of 14:52, 22 June 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')
local StringBuilder = require('Module:StringBuilder')
-- 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 SpecialItems = {
['GP'] = 'Gold Pieces',
['SC'] = 'Slayer Coins',
}
local SpecialIcons = {
['Gold Pieces'] = icons._Currency('melvorD:GP'),
['Slayer Coins'] = icons._Currency('melvorD:SlayerCoins'),
}
--- Possible fix to allow wikicode (especially tables) to be passed to the 'explanation'
--- parameter, and get parsed by the wiki parser on return.
-- Not used currently, because it might not be necessary at all.
local function processMarkup(str)
--return frame:preprocess(str)
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 formatItemName(name)
-- Special case to correctly format GP name.
if SpecialItems[name] then
return SpecialItems[name]
end
-- Got to solve too many edge case swith this (like of and IV)
-- Better just make the user use the correct name instead.
--if type(name) == 'string' then
-- return shared.titleCase(name)
--end
return name
end
local function formatSkillName(name)
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 SpecialIcons[iconName] then
return SpecialIcons[iconName]
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
local function parseProfitMargin(minProfit, maxProfit)
-- Manually set maxProfit to numeric 0, only if it's nil.
if maxProfit == nil then maxProfit = 0 end
local min = tonumber(minProfit)
local max = tonumber(maxProfit)
if max == nil then
error("maxProfit is not a valid number.")
end
local sb = StringBuilder:new()
if min then
sb :append(icons._Currency('melvorD:GP', num.round2(min)))
:append(' to ')
end
sb :append(icons._Currency('melvorD:GP', num.round2(max)))
:append(' per hour')
return sb:toString()
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 = formatItemName(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 = formatSkillName(args[skillPrefix])
local pExp = tonumber(args[skillPrefix .. AmountSuffix])
table.insert(skills, {
prmNumber = i,
name = pSkill,
exp = pExp})
end
-- Return nil if there is no experience earned.
-- Lets the parent table know to enter 'None' instead
if #skills == 0 then
return nil
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))
:css('height', '28px')
: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.autoround(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
:css('text-align','right')
:wikitext(icons._Currency('melvorD:GP', num.round2(tot)))
:attr("data-sort-value", 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 minProfit = args['minimumProfit']
local maxProfit = pOutputs.TotalValue - pInputs.TotalValue
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)
:css('font-weight', 'bold')
:wikitext(table.concat(dlcIcons) .. ' ')
:wikitext(args['guideName'] or '{{{guideName}}}')
:tag("tr")
:tag("td")
:attr("colspan", 2)
:wikitext(parseProfitMargin(minProfit, maxProfit))
:tag("tr")
:tag("th")
:attr("colspan", 2)
:wikitext("Requirements")
:tag('tr')
:tag('th')
:wikitext('Skills')
:tag('th')
:wikitext('Other')
:done()
:tag("tr")
:tag("td")
:addClass('mmg-no-list')
:newline()
:wikitext(paramtest.default_to(args['skills'], 'None'))
:tag("td")
:addClass('mmg-no-list')
:newline()
:wikitext(paramtest.default_to(args['other'], 'None'))
:tag('tr')
:tag('th')
:wikitext('Items')
:tag('th')
:wikitext('Recommended')
:tag('tr')
:tag('td')
:addClass('mmg-no-list')
:newline()
:wikitext(paramtest.default_to(args['items'], 'None'))
:tag('td')
:addClass('mmg-no-list')
:newline()
: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._Currency('melvorD:GP', num.round2(args['profit'])))
else
html:wikitext(icons._Currency('melvorD:GP', num.round2(maxProfit)))
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._Currency('melvorD:GP', num.round2(pInputs.TotalValue)) .. ")")
end
html = html
:tag("th")
:wikitext("Outputs")
if pOutputs.TotalValue ~= 0 then
html:wikitext(" (" .. icons._Currency('melvorD: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()
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 ='Mining Pure Crystal',
category ='Non-combat',
dlc ='aod, toth',
skills =[[
*{{SkillReq|Mining|85}}
*{{SkillReq|Herblore|53}}]],
items =[=[
*{{ItemIcon|Mining Gloves}}
*{{ItemIcon|Perfect Swing Potion IV}}]=],
other =[=[
*{{ItemIcon|Pure Crystal|notext=true}}{{Icon|Mastery|notext=true}} Level 99
*{{UpgradeIcon|Dragon Pickaxe}}
*[[Mining#Mastery Pool Checkpoints|95% Mining Mastery Pool Checkpoint]]]=],
recommended =[=[
[[Money_Making/Mining_Pure_Crystal#Improves_GP_Rate|Bonusses that improve profit]]]=],
}
local t = p._buildMMGTable(args)
mw.log(t)
end
-- function p.test()
-- local args = {
-- guideName = "",
-- interval = "",
-- skills = "",
-- items = "",
-- other = "",
-- recommended = "",
-- skillExp1 = "Maagic",
-- skillExp1amount = "-1000",
-- skillExp2 = "Mining",
-- skillExp2amount = "420",
-- input1 = "nature rune",
-- input1amount = "5",
-- input1value = "69",
-- input2 = "Fire Rune",
-- input2amount = "20",
-- output1 = "gp",
-- output1amount = "1050",
-- category = "",
-- dlc = "aod, toth",
-- intensity = "",
-- explanation = ""
-- }
--
-- mw.log(p._buildMMGTable(args))
-- end
return p