Модуль:Item

Материал из Terraria Wiki
Перейти к навигации Перейти к поиску
Lua.svg Документация Документация, указанная ниже, находится на странице «Модуль:Item/док». (править | история)
См. также этот модуль на английском языке: Module:Item. В нём может содержаться более полная или подробная информация.

Этот модуль предназначен для корректной работы шаблона {{item}}.


---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

-- Translation module
local tr = require('Module:Tr')
local pr = require('Module:Pronounce').getCase

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

local md5 = function(str)
	return mw.hash.hashValue('md5', str)
end

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

local function getCacheKey(arg)
	return 
	"@name:"..(arg[1] or '')..
	"^@text:"..(arg[2] or '')..
	"^@mode:"..(arg['mode'] or '')..
	"^@rowspan:"..(arg['rowspan'] or '')..
	"^@image:"..(arg['image'] or '')..
	"^@scale:"..(arg['scale'] or '')..
	"^@size:"..(arg['size'] or '')..
	"^@maxsize:"..(arg['maxsize'] or '')..
	"^@ext:"..(arg['ext'] or '')..
	"^@nolink:"..(arg['nolink'] and 'y' or '')..
	"^@link:"..(arg['link'] or '')..
	"^@icons:"..(arg['icons'] or '')..
	"^@small:"..(arg['small'] or '')..
	"^@note:"..(arg['note'] or '')..
	"^@note2:"..(arg['note2'] or '')..
	"^@bignote:"..(arg['bignote'] or '')..
	"^@lcfirst:"..(arg['lcfirst'] and 'y' or '')..
	"^@case:"..(arg['case'] and 'y' or '')..
	"^@paren:"..(arg['paren'] and 'y' or '')..
	"^@id:"..(arg['id'] or '')..
	"^@showid:"..(arg['showid'] or '')..
	"^@type:"..(arg['type'] or '')..
	"^@wrap:"..(arg['wrap'] or '')..
	"^@class:"..(arg['class'] or '')..
	"^@css:"..(arg['css'] or '')..
	"^@lang:"..(arg['lang'] or '')..
	"^@anchor:"..(arg['anchor'] or '')..
	"^"
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', {
		-- md5name for case-sensitive query.
		where = 'md5name='.. "'"..md5(imagename).."'",
		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)
	-- don't cache {{item}}'s result when parsing imagesize fails.
	should_cache = false
	local imageTitle = mw.title.new("File:" .. imagename)
	local width, height = imageTitle.file.width, imageTitle.file.height
	if width and width ~= 0 and height and height ~= 0 then
		should_cache = true -- ok, cache it.
		currentFrame:callParserFunction('#cargo_store:_table=Imageinfo',{
			image = imagename,
			md5name = md5(imagename),
			width = width,
			height = height,
			cached = os.time(),
		})
	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?
	local cache_key = args and getCacheKey(args) or (':_item:' .. frame.args[1])
	local cached = cache.get(cache_key)
	if cached then
		return cached
	end

	-- init var cache
	currentFrame = frame
	args_table = args or parse(frame.args[1])
	lang = getArg('lang') or 'en'
	local pageLang = mw.language.new(lang)
	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 tr.translateLink(_arg1, lang) -- now: _link == '' means nolink
	
	local caseText = (getArg('case') and not getArg(2)) and pr(tr.translate(_arg1, lang), getArg('case')) or nil
	
	local text = getArg(2)
	
	if text == 's' or text == 'es' or text == 'ies' then
		text = pr(tr.translate(_arg1, lang), 'ип_м') or text or caseText or tr.translate(_arg1, lang) or  ''
	else
		text = getArg(2) or caseText or tr.translate(_arg1, lang) or  ''
	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 hovertext
	if output_image and not output_text then
		-- with image only, the hovertext will only be displayed on the image, so it should be text or {{tr|_arg1}} or _link (in that order)
		if text ~= '' then
			hovertext = text
		elseif _arg1 ~= '' then
			hovertext = tr.translate(_arg1, lang)
		else
			hovertext = _link
		end
	else
		-- with image and text, the hovertext will be displayed on the image and on the text, so it should be {{tr|_arg1}} or text or _link (in that order)
		if _arg1 ~= '' then
			hovertext = tr.translate(_arg1, lang)
		elseif text ~= '' then
			hovertext = text
		else
			hovertext = _link
		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'
			elseif _arg1 == 'r/Terraria' then
				image_arg = 'r Terraria'
			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, _bignote, _showid, _id, _icon = getArg('note'), getArg('note2'), getArg('bignote'), 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: eicons
		local icon = ''
		if text ~= '' and _icon ~= 'n' and _icon ~= 'no' then -- no display text no eicons.
			local _small = (_showid or _note2 or _wrap or getArg('small')) and 'y'
			if _icon and _icon ~= 'y' and _icon ~= 'yes' then
				-- make the size of {{eicons}} from {{{icon}}} input match {{{small}}} setting.
				if _small then
					icon = string.gsub(_icon, ' class="eico ', ' class="eico s ')
				else
					icon = string.gsub(_icon, ' class="eico s ', ' class="eico ')
				end
			else
				icon = eicons(_arg1, lang, _small)
			end
		end
		
		if getArg('lcfirst') then
			text = pageLang:lcfirst(text)
		end
		
		local useParen = (string.sub(text, 1, 2) == '«' and string.sub(text, -2, -1) == '»')
		
		if useParen then
			text = text:sub(3, -3)
			if getArg('case') then
				text = pr(text, getArg('case'))
			end
		end
		
		-- prepare: link and display text
		if _link == '' or text == '' or string.find(text, '%[%[.-%]%]') then
			
			if getArg('paren') or useParen then
				text = l10n('quote_mark_left') .. text .. l10n('quote_mark_right')
			end
			
			text = '<span title="'..hovertext..'">'..text..'</span>'
		else
			if text == _link then
				text = '[['..text..']]'
			else
				if string.match(_link, 'https?://[%w-_%.%?%.:/%+=&]+') then
					text = '['.._link..' '..text..']'
				else
					text = '[['.._link..'|'..text..']]'
				end
			end
			
			if getArg('paren') or useParen then
				text = l10n('quote_mark_left') .. text .. l10n('quote_mark_right')
			end
			
			text = '<span>' .. text .. '</span>'
		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 .. '<span class="note2">' .. _note2 .. '</span>'
			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 .. '<span class="id">' .. id_text .. _id .. '</span>'
			end
		end
		if _bignote then
			text_output = '<span>' .. content .. '</span><span>' .. _bignote .. '</span>'
		else
			text_output = '<span>' .. content .. '</span>'
		end
	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

	-- anchor:
	local _anchor = getArg('anchor')

	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)
		if _anchor then
			first_cell_content = _anchor .. first_cell_content
		end
		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, _anchor and (image_output .. text_output .. _anchor) or (image_output .. text_output) )
	end

	-- cache output for reuse
	if should_cache then
		cache.set(cache_key, 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,

storeImageInfo = function(frame)
	currentFrame = frame
	local width, height = storeInfoToCargo(frame.args[1])
	if not width or width == 0 or not height or height == 0 then
		return
	else
		return frame:callParserFunction{name = "#dplvar:set", args = { 
			"_image_exist", "1",
			"_image_width", width,
			"_image_height", height,
		}}
	end
end,

}