Terraria Wiki

  • Discussions are now available on the Terraria Wiki.
  • Miss the old Hydra Skin? Try out our Hydralize gadget! Visit the preferences page while logged in and turn on the gadget.

READ MORE

Terraria Wiki
Register
(fixed an cache issue: if parsing imagesize failed, we should not cache the (incorrect) result.)
(added purge method.)
Line 494: Line 494:
 
return return_string
 
return return_string
   
  +
end,
  +
  +
purge = function(frame)
  +
cache.delete(':_item:' .. frame.args[1]) -- delete that cache key.
 
end,
 
end,
   

Revision as of 04:38, 4 March 2021

Lua logo Documentation The documentation below is transcluded from Module:Item/doc. (edit | history)

This module is intended to provide functionality to the {{item}} template.


---Holds the tables with the l10n information for the different languages, taken from the l10n submodule.
local l10n_info = mw.loadData('Module:Item/l10n')

---Holds the l10n information for the current language, as key-value pairs.
local l10n_table

---The current language. Determines which l10n table to use.
local lang

local trim = mw.text.trim
local cargo = mw.ext.cargo
local eicons = require('Module:Exclusive').simpleEicons
local cache = require 'mw.ext.LuaCache'

local should_cache = true

---A cached version of the current frame, the interface to the parser.
local currentFrame
---Holds the arguments from the template call.
local args_table


---Return the l10n string associated with the `key`.
---@param key string
---@return string
local function l10n(key)
	return l10n_table[key] or l10n_info['en'][key]
end

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

---Convert a string of parameters in a `@param1:value^@param2:value^` format to a table.
---Change the `name` and `text` parameters to `1` and `2`, respectively.
---@param paramstr string
---@return table
local function parse(paramstr)
	local args = {}
	for s in string.gmatch(paramstr, '%b@^') do
		local k,v = string.match(s, '^@(.-):(.*)^$')
		args[k] = v
	end
	args[1] = args['name']
	args[2] = args['text']
	return args
end

---Split the `str` on each `div` in it and return the result as a table.
---Original version credit: http://richard.warburton.it. This version trims each substring.
---@param div string
---@param str string
---@return table|boolean
local function explode(div,str)
	if (div=='') then return false end
	local pos,arr = 0,{}
	-- for each divider found
	for st,sp in function() return string.find(str,div,pos,true) end do
		arr[#arr + 1] = trim(string.sub(str,pos,st-1)) -- Attach chars left of current divider
		pos = sp + 1 -- Jump past current divider
	end
	arr[#arr + 1] = trim(string.sub(str,pos)) -- Attach chars right of last divider
	return arr
end

---Extract scale, width, and height from an input string. Up to two of the three can be empty in the input.
---Example: `5x7px*0.75` → `0.75`, `5`, `7`
---@param size string
---@return string basescale
---@return number width
---@return number height
local function parseSize(size)
	if not size then return end
	local basescale, width, height
	size, basescale = unpack(explode('*', size))
	if size ~= '' then
		width, height = unpack(explode('x', string.gsub(size, 'px', '')))
		width, height = tonumber(width), tonumber(height)
		if width == 0 then width = nil end
		if height == 0 then height = nil end
	end
	return basescale, width, height
end

---Return width, height, and caching date for the specified `imagename` from the Imageinfo cargo table.
---@param imagename string
---@return number width
---@return number height
---@return string cached
local function getInfoFromCargo(imagename)
	-- try to get from cargo cache
	local result = mw.ext.cargo.query('Imageinfo', 'width, height, cached', {
		-- escape apostrophes in the imagename input
		where = 'image='.. "'"..imagename:gsub("'", "\\'"):gsub("'", "\\'").."'",
		orderBy = "cached DESC",
		limit = 1,
	})
	for _, row in ipairs(result) do
		return tonumber(row['width']), tonumber(row['height']), row['cached']
	end
end

---Store width and height of the specified `imagename` to the Imageinfo cargo table and return them.
---Width and height are computed via the `#imgw:` and `#imgh:` parser functions, respectively.
---@param imagename string
---@return number width
---@return number height
local function storeInfoToCargo(imagename)
	local width, height
	-- don't cache {{item}}'s result when parsing imagesize fails.
	should_cache = false
	width = tonumber(currentFrame:callParserFunction( '#imgw', imagename))
	if width and width ~= 0 then -- save one expensive call when the file is not a valid image.
		height = tonumber(currentFrame:callParserFunction( '#imgh', imagename))
		if height and height ~= 0 then
			should_cache = true -- ok, cache it.
			currentFrame:callParserFunction('#cargo_store:_table=Imageinfo',{
				image = imagename,
				width = width,
				height = height,
				cached = os.time(),
			})
		end
	end
	return width, height
end

---Retrieve the dimensions of the specified `image` from the Imageinfo cargo table.
---If it doesn't have any data for the image yet, store it.
---@param imagename string
---@return number width
---@return number height
local function getSizeInfo(imagename)
	local width, height, cached = getInfoFromCargo(imagename)
	-- cache missed, init cache
	if not cached then
		width, height = storeInfoToCargo(imagename)
	end
	if width == 0 then width = nil end
	if height == 0 then height = nil end
	return width, height
end

---Compute the final width and height of the image.
---If necessary, retrieve data from or store data to the Imageinfo cargo table.
---@param imagename string
---@param width number
---@param height number
---@param scale number
---@param maxwidth number
---@param maxheight number
---@return number width
---@return number height
local function getImageSize(imagename, width, height, scale, maxwidth, maxheight)
	-- get size info from image file itself (may be expensive)
	local w, h = getSizeInfo(imagename) -- store data to cache

	-- if width and height are not given as input, but scale/maxwidth/maxheight are, then
	-- set width and height to the original dimensions of the image
	if not width and not height and (scale or maxwidth or maxheight) then
		width, height = w, h
	end

	-- apply scale to width/height if needed
	if scale then
		if width then width = width * scale end
		if height then height = height * scale end
	end

	-- apply maxwidth/maxheight
	if maxwidth then
		if width then
			if width > maxwidth then width = maxwidth end
		else
			if height then width = maxwidth end
		end
	end
	if maxheight then
		if height then
			if height > maxheight then height = maxheight end
		else
			if width then height = maxheight end
		end
	end

	-- round to natural numbers
	if width then width = math.ceil(width) end
	if height then height = math.ceil(height) end

	return width, height
end

---Extract width and height from an input string.
---Example: `6x9px` → `6`, `9`
---@param maxsize string
---@return number maxwidth
---@return number maxheight
local function parseMaxSize(maxsize)
	if not maxsize then return end
	local maxwidth, maxheight = unpack(explode('x', string.gsub(maxsize, 'px', '')))
	maxwidth, maxheight = tonumber(maxwidth), tonumber(maxheight)
	if maxwidth == 0 then maxwidth = nil end
	if maxheight == 0 then maxheight = nil end
	return maxwidth, maxheight
end

---Assemble the final wikicode for an image.
---@param imagename string
---@param link string
---@param text string
---@param size string As accepted by the `[[File:` syntax, e.g. `5x7px*0.75`.
---@param scale number This will be multiplied by the scale in `size`, if necessary.
---@param maxsize string
---@return string
local function imagecode(imagename, link, text, size, scale, maxsize)
	local image_output = '[[File:' .. imagename .. '|link='.. link .. '|' .. text
	if size or scale or maxsize then
		local basescale, width, height = parseSize(size) -- width, height: number or nil (basescale is string!)
		scale = (tonumber(scale) or 1) * (tonumber(basescale) or 1) -- combine the scale parameter and scale from the size parameter
		if scale == 0 or scale == 1 then
			scale = nil
		end
		local maxwidth, maxheight = parseMaxSize(maxsize)
		width, height = getImageSize(imagename, width, height, scale, maxwidth, maxheight) -- can be 0
		if width or height then
			image_output = image_output .. '|' .. (width or '') .. 'x' .. (height or '') .. 'px'
		end
	end
	return image_output .. ']]'
end

---Return the full `[[File:` wikicode for each image in the input (multiple are separated with `/`).
---@param image string
---@param link string
---@param text string
---@param size string
---@param scale string
---@param maxsize string
---@return string
local function images(image, link, text, size, scale, maxsize)

	if not image:find('/') then
		-- there is only one image in the input
		return imagecode(image, link, text, size, scale, maxsize)
	end

	-- there are multiple images in the input, separated with a slash
	image = explode('/', image)
	local result = ''
	if size and size:find('/') then
		-- there are multiple sizes in the size parameter
		size = explode('/', size) -- so turn it into a table
		for i, v in ipairs(image) do -- iterate over the images
			result = result .. imagecode(v, link, text, size[i], scale, maxsize) -- create the wikicode (using the respective size)
		end
	else
		for i, v in ipairs(image) do -- iterate over the images
			result = result .. imagecode(v, link, text, size, scale, maxsize) -- create the wikicode
		end
	end
	return result
end

---Return a string like `Internal Item ID: `, depending on the `_type`.
---@param _type '"item"'|'"tile"'|'"wall"'|'"npc"'|'"mount"'|'"buff"'|'"projectile"'|'"armor"'
---@return string
local function getIdText(_type)
	local id_text
	if _type == 'item' then -- a shortcut for faster
		id_text = l10n('id_text_item')
	elseif _type == 'tile' then
		id_text = l10n('id_text_tile')
	elseif _type == 'wall' then
		id_text = l10n('id_text_wall')
	elseif _type == 'npc' then
		id_text = l10n('id_text_npc')
	elseif _type == 'mount' then
		id_text = l10n('id_text_mount')
	elseif _type == 'buff' or _type == 'debuff' then
		id_text = l10n('id_text_buff')
	elseif _type == 'projectile' then
		id_text = l10n('id_text_projectile')
	elseif _type == 'armor' then
		id_text = l10n('id_text_armor')
	else
		id_text = l10n('id_text_item')
	end
	return id_text
end

-----------------------------------------------------------------
-- main return object
return {

parse = parse,
go = function(frame, args)
	-- cache?
	if not args then
		local cached = cache.get(':_item:' .. frame.args[1])
		if cached then
			return cached
		end
	end

	-- init var cache
	currentFrame = frame
	args_table = args or parse(frame.args[1])
	lang = getArg('lang') or 'en'
	l10n_table = l10n_info[lang] or l10n_info['en']

	local _arg1 = getArg(1) or ''
	local _nolink = getArg('nolink')
	local _link = _nolink and '' or getArg('link') or frame:expandTemplate{ title = 'tr', args = {_arg1, link='y', lang=lang} } -- now: _link == '' means nolink

	local text = getArg(2) or ''

	-- hovertext: {{tr|_arg1}} or text or _link (in that order)
	local hovertext
	if _arg1 ~= '' then
		hovertext = frame:expandTemplate{ title = 'tr', args = {_arg1, lang=lang} }
	elseif text ~= '' then
		hovertext = text
	else
		hovertext = _link
	end

	-- set output flags
	local output_image, output_text, output_table = true, true, false
	local _mode = getArg('mode')
	if _mode then
		if _mode == 'image' or _mode == 'imageonly' or _mode =='onlyimage' then
			output_text = false
		elseif _mode == 'text' or _mode == 'noimage' then
			output_image = false
		elseif _mode == 'table' or _mode == '2-cell' then
			output_table = true
		end
	end

	local class = 'i'

	local image_output, text_output
	-- get wikicode for the image(s)
	if output_image then
		local image_arg = getArg('image')
		if not image_arg then
			if _arg1 == '1/2 Second Timer' then
				image_arg = '1 2 Second Timer'
			elseif _arg1 == '1/4 Second Timer' then
				image_arg = '1 4 Second Timer'
			else
				image_arg = string.gsub(_arg1, ":%s*", " ")
			end
			image_arg = image_arg .. '.' .. (getArg('ext') or 'png')
		end
		if string.find(image_arg, '%[%[[fF]ile:') then
			image_output = '<span class="img">' .. image_arg .. '</span>'
		else
			image_output = images(image_arg, _link, hovertext, getArg('size'), getArg('scale'), getArg('maxsize'))
		end
	else
		image_output = ''
	end
	-- get wikicode for the text
	if output_text then
		local _note, _note2, _showid, _id, _icon = getArg('note'), getArg('note2'), getArg('showid'), getArg('id'), getArg('icons') -- get info from arguments

		-- prepare: display ID?
		if _id and not _showid then
			_showid = true
		end
		if _showid and (_showid == 'n' or _showid == 'no') then
			_showid = false
		end

		-- prepare: wrap?
		local _wrap
		if _showid or _note2 then
			_wrap = false
		else
			_wrap = getArg('wrap')
		end

		-- prepare: link and display text
		if _link ~= '' then
			if text == _link then
				text = '<span>[['..text..']]</span>'
			else
				text = '<span>[['.._link..'|'..text..']]</span>'
			end
		else
			text = '<span title="'..hovertext..'">'..text..'</span>'
		end

		-- prepare: eicons
		local icon = nil
		if _icon == 'n' or _icon == 'no' or _icon == 'off' then
			icon = ''
		else
			icon = eicons(getArg('epage') or _arg1, lang, (_showid or _note2 or _wrap or getArg('small')) and 'y')
		end

		-- assemble HTML code
		local content = text -- item name link text first.
		-- '-w' class means 'wrapmode', optimized for multiple lines of text. But it should be disabled for single line text.
		if _wrap then
			-- eicons in the same line
			if icon ~= '' then
				class = class .. ' -w'
				content = content .. icon
			end
			-- note in a new line
			if _note then
				class = class .. ' -w'
				content = content .. '<span class="note">' .. _note .. '</span>'
			end
		else
			-- note in the same line
			if _note then
				content = content .. '<span class="note">' .. _note .. '</span>'
			end
			-- eicons in the same line
			if icon ~= '' then
				content = content .. icon
			end
			-- note2 in a new line
			if _note2 then
				class = class .. ' -w'
				content = content .. '<div class="note">' .. _note2 .. '</div>'
			end
			-- id in a new line
			if _showid then
				class = class .. ' -w'
				local idtype = (getArg('type') or 'item'):lower()
				if not _id then
					-- get ID automatically via {{itemIdFromName}} or the like
					_id = frame:expandTemplate{ title = idtype .. 'IdFromName', args = {_arg1} }
				end
				local id_text = getIdText(idtype)
				content = content .. '<div class="id">' .. id_text .. _id .. '</div>'
			end
		end
		text_output = '<span>' .. content .. '</span>'
	else
		text_output = ''
	end

	-- handle custom CSS
	local _class, _css = getArg('class'), getArg('css')
	if _class then
		class = class .. ' ' .. _class -- add to existing classes
	end
	local attr = {class = class}
	if _css then
		attr.style = _css -- set the style attribute to parameter value
	end

	local return_string
	if output_table then
		-- table output
		attr.class = class
		local _rowspan = getArg('rowspan')
		local rowspan_text = (_rowspan and (' rowspan=' .. _rowspan) or '')
		-- prepare the two cells
		local first_cell_pre = rowspan_text .. ' class="il1c"'
		local first_cell_content = mw.text.tag('span', attr, image_output)
		local second_cell_pre = rowspan_text .. ' class="il2c"'
		local second_cell_content = mw.text.tag('span', attr, text_output)
		-- combine
		return_string = first_cell_pre .. " | " .. first_cell_content .. " || " .. second_cell_pre .. " | " .. second_cell_content
	else
		-- non-table output (text/image)
		return_string = mw.text.tag('span', attr, image_output .. text_output)
	end

	-- cache output for later reuse
	if not args and should_cache then
		cache.set(':_item:' .. frame.args[1], return_string, 3600*24) -- cache for 24 hours
	end

	-- output
	return return_string

end,

purge = function(frame)
	cache.delete(':_item:' .. frame.args[1]) -- delete that cache key.
end,

}