Fandom

Old School RuneScape Wiki

Module:Exchange

16,282pages on
this wiki
Add New Page
Discuss0
--[[
{{Helper module|name=Exchange
|fname1=_price(arg)
|ftype1=String
|fuse1=Gets the current median price of item named arg
|fname2=_value(arg)
|ftype2=String
|fuse2=Gets the value of item named arg
}}
--]]
-- <pre>
--
-- Implements various exchange templates
-- See Individual method docs for more details
--
-- See also:
-- - [[Module:ExchangeData]]
-- - [[Module:ExchangeDefault]]
-- 
-- Original version: http://runescape.wikia.com/wiki/Module:Exchange
 
local p = {}
 
-- only load commonly used modules here
local yesno = require( 'Module:Yesno' )
local addcommas = require( 'Module:Addcommas' )._add
 
-- map redirects to their correct pages
local geRedirects = {
    ['1/2 anchovy pizza'] = '½ anchovy pizza',
    ['1/2 meat pizza'] = '½ meat pizza',
    ["1/2 p'apple pizza"] = "½ p'apple pizza",
    ['1/2 plain pizza'] = '½ plain pizza',
    ['1/3 evil turnip'] = '⅓ evil turnip',
    ['2/3 cake'] = '⅔ cake',
    ['2/3 chocolate cake'] = '⅔ chocolate cake',
    ['2/3 evil turnip'] = '⅔ evil turnip'
}
 
--
-- Makes sure first letter of item is uppercase
-- Automatically handles any redirects
--
local function checkTitle( item )
    -- upper case first letter to make sure we can find a valid item page
    item = mw.text.split( item, '' )
    item[1] = mw.ustring.upper( item[1] )
    item = table.concat( item )
 
    -- automatically handle redirects
    if geRedirects[item] ~= nil then
        item = geRedirects[item]
    end
 
    return item
end
--
-- Simple mw.loadData wrapper used to access data located on module subpages
--
-- @param item {string} Item to retrieve data for
-- @return {table} Table of item data
--
local function load( item )
    item = checkTitle( item )
    local noErr, ret = pcall( mw.loadData, 'Module:Exchange/' .. item )
 
    if noErr then
        return ret
    end
 
    error( ret )
end
 
--
-- Returns the price of an item
--
-- @param item {string} Item to get current price of
-- @param multi {number} (optional) Multiplies the output price by the specified number
-- @param format {boolean} (optional) Format the result with commas (defaults to false)
-- @param round {number} (optional) Round the result to a number of decimal places
-- @return {number|string} Price of item. Will return a string if formatted, else a number.
--
function p._price( item, multi, format, round )
    local price = load( item ).price
    local multi = type( multi ) == 'number' and multi or 1
    local format = type( format ) == 'boolean' and format or false
    local ret = price * multi
 
    -- round the number to X d.p.
    if round ~= nil then
        local multi = 10^( round )
        ret = math.floor( ret * multi + 0.5 ) / multi
    end
 
    if format then
        return addcommas( ret )
    end
 
    return ret
end
 
--
-- Returns the limit of an item
--
-- @param item {string} Item to get the limit of
-- @return {number} Limit of item
--
function p._limit( item )
    return load( item ).limit
end
 
--
-- Returns the value of an item
--
-- @param item {string} Item to get the value or
-- @return {number} Value of item
--
function p._value( item )
    return load( item ).value
end
 
--
-- Calculates the difference between the current price and the last price of an item
--
-- @param item {string} Item to calculate price difference for
-- @param format {boolean} `true` if the output is to be formatted with commas
--                         Defaults to `false`
-- @return {number|string} The price difference as a number
--                         If `format` is set to `true` then this returns a string
--                         If either of the prices to calculate the diff from are unavailable, this returns `0` (number)
--
function p._diff( item, format )
    local data = load( item )
    local diff = 0
 
    if data.price and data.last then
        diff = data.price - data.last
 
        if format then
            diff = addcommas( diff )
        end
    end
 
    return diff
end
 
--
-- {{GEItem}} internal method
--
-- @todo merge into p.table
--
-- @param item {string} Item to get data for
-- @return {string}
--
function p._table( item )
    -- load data and any required modules
    local item = checkTitle( item )
    local data = load( item )
    local timeago = require( 'Module:TimeAgo' )._ago
    local changeperday = require( 'Module:ChangePerDay' )._change
 
    -- set variables here to make the row building easier to follow
    local div = '<i>Unknown</i>'
    local limit = data.limit and addcommas( data.limit ) or '<i>Unknown</i>'
    local members = '<i>Unknown</i>'
 
    if data.last then
        local link = 'http://services.runescape.com/m=itemdb_oldschool/viewitem.ws?obj=' .. data.itemId
        local change = math.abs( changeperday( {data.price, data.last, data.date, data.lastDate} ) )
 
        if data.price > data.last then
            arrow = '[[File:Up.png|20px|link=' .. link .. ']]'
        elseif data.price < data.last then
            arrow = '[[File:Down.png|20px|link=' .. link .. ']]'
        else
            arrow = '[[File:Unchg.png|40px|link=' .. link .. ']]'
        end
 
        if change >= 0.04 then
            arrow = arrow  .. arrow .. arrow
        elseif change >= 0.02 then
            arrow = arrow .. arrow
        end
 
        div = mw.html.create( 'div' )
            :css( 'white-space', 'nowrap' )
            :wikitext( arrow )
 
        div = tostring( div )
    end
 
    if data.members == true then
        members = '[[File:P2P icon.png|30px|link=Members]]'
    elseif data.members == false then
        members = '[[File:F2P icon.png|30px|link=Free-to-play]]'
    end
 
    -- build table row
    local tr = mw.html.create( 'tr' )
        :tag( 'td' )
            :wikitext( '[[File:' .. item .. '.png|' .. item .. ']]' )
            :done()
        :tag( 'td' )
            :css( {
                ['width'] = '15%',
                ['text-align'] = 'left'
            } )
            :wikitext( '[[' .. item .. ']]' )
            :done()
        :tag( 'td' )
            :wikitext( addcommas( data.price ) )
            :done()
        :tag( 'td' )
            :wikitext( div )
            :done()
 
    if data.alchable == nil or yesno( data.alchable ) then
        local low, high = '<i>Unknown</i>', '<i>Unknown</i>'
 
        if data.value then
            low = addcommas( math.floor( data.value * 0.4 ) )
            high = addcommas( math.floor( data.value * 0.6 ) )
        end
 
        tr
            :tag( 'td' )
                :wikitext( low )
                :done()
            :tag( 'td' )
                :wikitext( high )
                :done()
    else
        tr
            :tag( 'td' )
                :attr( 'colspan', '2' )
                :wikitext( '<i>Cannot be alchemised</i>' )
                :done()
    end
 
    tr
        :tag( 'td' )
            :wikitext( limit )
            :done()
        :tag( 'td' )
            :wikitext( members )
            :done()
        :tag( 'td' )
            :css( 'white-space', 'nowrap' )
            :wikitext( '[[Exchange:' .. item .. '|view]]' )
            :done()
        :tag( 'td' )
            :css( 'font-size', '85%' )
            :wikitext( timeago{data.date} )
            :done()
 
    return tostring( tr )
 
end
 
--
-- {{GEExists}}
--
function p.exists( frame )
    local args = frame:getParent().args
    local item = checkTitle( args[1] or '' )
    local noErr, data = pcall( mw.loadData, 'Module:Exchange/' .. item )
 
    if noErr then
        return '1'
    end
 
    return '0'
end
 
--
-- Internal method for p.highAlchTable, p.lowAlchTable and p.genStoreTable
--
-- @param item {string} The name of the item
-- @param data {table} The item's ge data
-- @param alch {number} The item's alch/sell value
-- @param min {number} (optional) Sets the cap for amount of items that can be converted to gp per hour
-- @param natPrice {number} (optional) Sets the price of a Nature rune (set to `0` by `p.genStoreTable`)
--
local function alchTable( item, data, alch, min, natPrice )
    local timeago = require( 'Module:TimeAgo' )._ago
    local round = require( 'Module:Number' )._round
    -- gen store doesn't need a nat price as it's not used
    -- therefore we'd set it to 0
    local natPrice = natPrice or load( 'Nature rune' ).price
    local profit = alch - data.price - natPrice
 
    local image = '[[File:' .. item .. '.png|' .. item .. ']]'
    local itemStr = '[[' .. item .. ']]'
    local priceStr = addcommas( data.price )
    local alchStr = addcommas( alch )
    local profitStr = addcommas( profit )
    local roi = tostring( round( ( profit / ( data.price + natPrice ) * 100 ), 1 ) ) .. '%'
    local limit = data.limit and addcommas( data.limit ) or '<i>Unknown</i>'
    local maxProfit = '<i>Unknown</i>'
    local members = '<i>Unknown</i>'
    local details = '[[Exchange:' .. item .. '|view]]'
    local lastUpdated = timeago{data.date}
 
    if data.limit then
        -- cap at 4800, the maximum number of alchs that can be cast every 4 hours
        -- varies for general store rows
        min = min or 4800 
        min = ( data.limit > min ) and min or data.limit
        maxProfit = addcommas( min * profit )
    end
 
    mw.log( maxProfit )
 
    if data.members == true then
        members = '[[File:P2P.png|30px|link=Members]]'
    elseif data.members == false then
        members = '[[File:F2P icon.png|30px|link=Free-to-play]]'
    end
 
    local tr = mw.html.create( 'tr' )
        :tag( 'td' )
            :wikitext( image )
            :done()
        :tag( 'td' )
            :css( {
                width = '15%',
                ['text-align'] = 'left'
            } )
            :wikitext( itemStr )
            :done()
        :tag( 'td' )
            :wikitext( priceStr )
            :done()
        :tag( 'td' )
            :wikitext( alchStr )
            :done()
        :tag( 'td' )
            :wikitext( profitStr )
            :done()
        :tag( 'td' )
            :wikitext( roi )
            :done()
        :tag( 'td' )
            :wikitext( limit )
            :done()
        :tag( 'td' )
            :wikitext( maxProfit )
            :done()
        :tag( 'td' )
            :wikitext( members )
            :done()
        :tag( 'td' )
            :css( 'white-space', 'nowrap' )
            :wikitext( details )
            :done()
        :tag( 'td' )
            :css( 'font-size', '85%' )
            :wikitext( lastUpdated )
            :done()
 
    return tostring( tr )
end
 
--
-- {{HighAlchTableRow}}
--
-- @example {{HighAlchTableRow|<item>}}
--
function p.highAlchTable( frame )
    local args = frame:getParent().args
    local item = checkTitle( args[1] )
    local data = load( item )
    local alch = math.floor( data.value * 0.6 )
 
    return alchTable( item, data, alch )
end
 
--
-- {{LowAlchTableRow}}
--
-- @example {{LowAlchTableRow|<item>}}
--
function p.lowAlchTable( frame )
    local args = frame:getParent().args
    local item = checkTitle( args[1] )
    local data = load( item )
    local alch = math.floor( data.value * 0.4 )
 
    return alchTable( item, data, alch )
end
 
--
-- {{GenStoreTableRow}}
--
-- @example {{GenStoreTableRow|<item>}}
--
function p.genStoreTable( frame )
    local args = frame:getParent().args
    local item = checkTitle( args[1] )
    local data = load( item )
    local alch = math.floor( data.value * 0.3 )
 
    return alchTable( item, data, alch, 50000, 0 )
end
 
--
-- {{GEP}}
-- {{GEPrice}}
--
-- @example {{GEPrice|<item>|<format>|<multi>}}
-- @example {{GEPrice|<item>|<multi>}}
-- @example {{GEP|<item>|<multi>}}
--
function p.price( frame )
    -- usage: {{foo|item|format|multi}} or {{foo|item|multi}}
    local args = frame.args
    local pargs = frame:getParent().args
    local item = pargs[1]
    local expr = mw.ext.ParserFunctions.expr
    local round = tonumber( pargs.round )
 
    if item then
        item = mw.text.trim( item )
    else
        error( '"item" argument not specified', 0 )
    end
 
    -- default to formatted for backwards compatibility with old GE templates
    local format = true
    local multi = 1
 
    -- format is set with #invoke
    -- so set it first to allow it to be overridden by template args
    if args.format ~= nil then
        format = yesno( args.format )
    end
 
    if tonumber( pargs[2] ) ~= nil then
        multi = tonumber( pargs[2] )
 
    -- indicated someone is trying to pass an equation as a mulitplier
    -- known use cases are fractions, but pass it to #expr to make sure it's handled correctly
    elseif pargs[2] ~= nil and mw.ustring.find( pargs[2], '[/*+-]' ) then
        multi = tonumber( expr( pargs[2] ) )
 
    -- uses elseif to prevent something like {{GEP|Foo|1}}
    -- causing a formatted output, as 1 casts to true when passed to yesno
    elseif type( yesno( pargs[2] ) ) == 'boolean' then
        format = yesno( pargs[2] )
 
        if tonumber( pargs[3] ) ~= nil then
            multi = tonumber( pargs[3] )
        end
    end 
 
    return p._price( item, multi, format, round )
end
 
--
-- {{GEItem}}
--
-- @example {{GEItem|<item>}}
--
function p.table( frame )
    local args = frame:getParent().args
    local item = args[1]
 
    if item then
        item = mw.text.trim( item )
    else
        error( '"item" argument not specified', 0 )
    end
 
    return p._table( item )
end
 
--
-- experimental limit method for [[Grand Exchange/Buying Limits]]
--
function p.gemwlimit( frame )
    local item  = frame:getParent().args[1]
    local data = mw.loadData( 'Module:Exchange/' .. item )
 
    return data.limit
end
 
--
-- {{ExchangeItem}}
-- {{GEDiff}}
-- {{GELimit}}
-- {{GEValue}}
-- {{GEId}}
--
-- @example {{ExchangeItem|<item>}}
-- @example {{GEDiff|<item>}}
-- @example {{GELimit|<item>}}
-- @example {{GEValue|<item>}}
-- @example {{GEId|<item>}}
--
function p.view( frame )
    local fargs = frame.args
    local pargs = frame:getParent().args
    local item = pargs[1]
    local view = fargs.view or ''
    local loadView = {limit=true, value=true, itemId=true}
 
    if item then
        item = mw.text.trim( item )
    else
        error( '"item" argument not specified', 0 )
    end
 
    view = mw.ustring.lower( view )
 
    if view == 'itemid' then
        view = 'itemId'
    end
 
    if view == 'diff' then
        return p._diff( item )
 
    elseif loadView[view] then
        return load( item )[view]
 
    else
        local default = require( 'Module:ExchangeDefault' )
        -- handle redirects and casing of item before passing it on
        item = checkTitle( item )
        return default.main( item )
    end
end
 
return p

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.

Also on Fandom

Random Wiki