Module:Coin expr

From Terraria Wiki
Jump to navigation Jump to search
Important.svg
CAUTION: Terraria Wiki code is complex!!!
If you want to use this code on another wiki, wiki.gg staff are not able to assist you.
Please consider picking a different wiki to adapt code from, or making your own templates!
Remember that content on a wiki is more important than fancy formatting.
Lua.svg Documentation The documentation below is transcluded from Module:Coin expr/doc. (edit | history)

This module provides the functionality of the {{sell expr}} and {{buy expr}} templates.

Usage

Wikitext

The module can be called from wikitext with the functions listed below.

go

{{#invoke:Coin expr| go | sell = y/yes/1/true }}

Performs the calculation. By default, the function uses the items' buy prices. If sell is true, then it uses the items' sell values (by dividing the buy prices by 5).

This function is generally only intended to be called from the templates mentioned above. The parameters passed in the transclusions of those templates (e.g. {{sell expr|raw=yes|True Excalibur|+|True Night's Edge}}) are recognized and handled by this function. See the template documentation for details about the parameters.


--------------------------------------------------------------------------------
--
-- =============================================================================
--
-- Module:Coin expr
--
-- Calculation of expressions that involve dynamic item values
--
-- =============================================================================
--
-- Code annotations:
-- This module is documented according to LuaCATS (Lua Comment and Type System).
-- LuaCATS comments are prefixed with three dashes (---) and use Markdown syntax.
-- For a full list of annotations, see the following link:
-- https://luals.github.io/wiki/annotations/
--
--------------------------------------------------------------------------------

local trim = mw.text.trim
local iteminfo = require('Module:Iteminfo')
local itemNameData = require('Module:ItemNames').getData

---A cached version of the current frame, the interface to the parser.
local currentFrame

---Holds the arguments from the template call.
---@type table<string, string>
local inputArgs

---Whether the module should compute sell values of items, instead of buy prices.
local isSellExpr = false

---Return a trimmed version of the value of the template parameter with the specified `key`.
---Return `nil` if the parameter is unset.
---@param key string|integer
---@return string?
local function getArg(key)
	local value = inputArgs[key]
	if not value then
		return nil
	end
	value = trim(value)
	return value
end

---Conversion table for the `getBoolArg` function.
---@type table<string, boolean>
local stringToBoolean = {
	['y'] = true, ['yes'] = true, ['1'] = true, ['on'] = true, ['true'] = true,
	['n'] = false, ['no'] = false, ['0'] = false, ['off'] = false, ['false'] = false,
}

---Convert the value of the template parameter with the specified `key` to a Boolean.
---Return `default` or `nil` if the value is not a recognized Boolean string.
---@param key string|integer
---@param default boolean?
---@return boolean?
local function getBoolArg(key, default)
	local value = getArg(key)
	if stringToBoolean[value] ~= nil then
		return stringToBoolean[value]
	end
	return default
end

---List of strings about erroneous parameters.
---@type string[]
local argErrors = {}

---Mark a parameter as erroneous and add an error string about it.
---@param argValue string Erroneous value of the parameter
---@param argIndex integer Index of the parameter
local function argError(argValue, argIndex)
	argErrors[#argErrors + 1] = 'Parameter ' .. argIndex .. ' is not a valid item name or ID (Value: <code>' .. argValue .. '</code>)! '
end

---Combine all the error strings that were collected previously via `argError()`
---and format them with `{{error}}`.
---@return string
local function errorOutput()
	local templateName = (isSellExpr and 'sell' or 'buy') .. ' expr'
	local templateLink = currentFrame:expandTemplate{ title = 'tl', args = {templateName} }
	local errorStr = 'Error in ' .. templateLink .. ': ' .. table.concat(argErrors)
	return currentFrame:expandTemplate{ title = 'error', args = {errorStr} }
end

---Convert the input parameter to an item ID. Recognizes valid item ID strings
---and valid English item names. If the `itemNameOrId` is neither, then return
---`nil`.
---@param itemNameOrId string Input parameter value
---@return number?
local function convertToItemId(itemNameOrId)
	-- check: is `itemNameOrId` an item ID string?
	if (
		-- is it a number?
		type(tonumber(itemNameOrId)) == 'number'
		-- does it not start with '+' nor '-'?
		-- (`tonumber` accepts '+' and '-' at the start of the string, so we
		-- need to explicitly exclude that)
		and string.byte(itemNameOrId) ~= 43 and string.byte(itemNameOrId) ~= 45
		-- is it in the range of valid item IDs?
		and iteminfo.info.IDs.isValidWithUnused(tonumber(itemNameOrId)))
	then
		-- check succeeded
		return tonumber(itemNameOrId)
	end
	-- check: is `itemNameOrId` an English item name string?
	local itemId = itemNameData('itemIdFromName', itemNameOrId)
	if itemId and itemId ~= '' then
		-- check succeeded
		return tonumber(itemId)  -- itemIdFromName returns the ID as a string
	end
	-- both checks failed, we don't recognize `itemNameOrId` and cannot convert
	-- it to an item ID
	return nil
end

---Return the coin value of an item as a string for the expression string.
---@param itemNameOrId string Input parameter value
---@param argIndex integer Parameter index
---@return string coinvalue
local function coinvalueOfItem(itemNameOrId, argIndex)
	if itemNameOrId == '' then
		return ''
	end
	local itemid = convertToItemId(itemNameOrId)
	if itemid then
		local coinvalue = iteminfo.getItemStat(itemid, 'value')
		if isSellExpr then
			coinvalueFloored = math.floor(coinvalue / 5)
			-- do not round to below 1
			-- though if the value was already 0 before rounding, then keep that
			if coinvalue > 0 and coinvalueFloored == 0 then
				coinvalueFloored = 1
			end
			coinvalue = coinvalueFloored
		end
		return tostring(coinvalue)
	end
	-- the parameter is not a recognized item ID or English item name, so add an
	-- error string about this
	argError(itemNameOrId, argIndex)
	return ''
end

--------------------------------------------------------------------------------
---Main return object
local p = {}

---@param frame table Interface to the parser (`mw.frame`)
p.go = function(frame)
	currentFrame = frame  -- global frame cache
	inputArgs = currentFrame:getParent().args  -- global input args cache

	local sellArg = currentFrame.args['sell']
	if stringToBoolean[sellArg] ~= nil then
		isSellExpr = stringToBoolean[sellArg]
	end

	local exprStr = ''

	-- iterate over all parameters and assemble the expression string
	-- (e.g. {'Terra Blade', '+ 5*', 'Torch'} => '200000 + 5* 10')
	local argIndex = 0
	while true do
		argIndex = argIndex + 1
		local arg = getArg(argIndex)
		if arg == nil then
			-- we reached the end of the parameters
			break
		end
		-- the parameters 1, 3, 5, ... are item names or IDs; the parameters
		-- 2, 4, 6, ... are expression syntax
		if argIndex % 2 == 0 then
			-- the current parameter is just expression syntax, so simply append
			-- it to the expression
			exprStr = exprStr .. arg
		else
			-- the current parameter is an item name or ID, so append its coin
			-- value to the expression
			exprStr = exprStr .. coinvalueOfItem(arg, argIndex)
		end
	end

	if #argErrors > 0 then
		return errorOutput()
	end

	-- parse and compute the expression (e.g. '200000 + 5* 10' => '200050')
	local success, resultStr = pcall(mw.ext.ParserFunctions.expr, exprStr)
	if getBoolArg('raw') then
		assert(success, 'input parameters contain invalid #expr: syntax')
		return resultStr
	else
		if not success then
			resultStr = '0'
		end
		-- format the result with `{{coin}}`
		local round = getArg('round') or 999
		return currentFrame:expandTemplate{ title = 'coin', args = {resultStr, round=round} }
	end
end

return p