Terraria Wiki

Miss the old Hydra Skin? Try out our Hydralize gadget! Visit the preferences page while logged in and turn on the gadget.


Terraria Wiki
Lua logo.svg Documentation The documentation below is transcluded from Module:Exclusive/doc. (edit | history)

This module is the core of the dynamic platform exclusivity system.

The basic function to invoke from templates is getInfo, which queries the exclusivity information database and stores the result to a set of dplvars. These are named in the format ex_<platform>, e.g. ex_d for Desktop, and they are either empty or set to y. The function does not produce any output.

The two other functions, eicons and simpleEicons, return the HTML code for the icons denoting platform exclusivity. The first function is intended to be used only by the {{eicons}} template, the second function can be used from any other module that includes platform exclusivity icons (such as Module:Item).

Important note: This module relies entirely on the database Module:Exclusive/data. The database is not updated automatically and instead requires periodic manual updates. Please see its documentation for instructions.

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

---Database with exclusivity info.
local exclusive_info = mw.loadData('Module:Exclusive/data')

local bit32 = require('bit32')
local trim = mw.text.trim

---Default content language of the wiki (i.e. `$wgLanguageCode`, not the value of the
---`uselang` URL parameter, and not the user's language preference setting).
local contentLanguage = mw.getContentLanguage()

---Holds the arguments from the template call.
local args_table

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

---Return the l10n string associated with the `key`.
---@param key string
---@return string
local function l10n(key)
	if l10n_data[lang] then
		return l10n_data[lang][key] or l10n_data['en'][key]
		return l10n_data['en'][key]

---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 = trim(args_table[key] or '')
	return (value ~= '') and value or nil

---Convert a string of parameters in a `@param1:value^@param2:value^` format to a table.
---@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
	return args

---Split the `str` on each `div` in it and return the result as a table.
---This is much much faster then `mw.text.split`.
---Credit: http://richard.warburton.it.
---@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] = string.sub(str,pos,st-1) -- Attach chars left of current divider
		pos = sp + 1 -- Jump past current divider
	arr[#arr + 1] = string.sub(str,pos) -- Attach chars right of last divider
	return arr

---Return the integer defined in the database for the specified `page`.
---Perform some standardization on the `page` for that first.
---@param page string
---@return number
local function readFromDb(page)
	-- standardize pagename: remove section parts ('x#section' -> 'x') and replace underscores with spaces
	page = contentLanguage:ucfirst(string.gsub(string.gsub(page or '', '#.*', ''), '_', ' '))
	return exclusive_info[page] or 0

---Override the exclusivity information in `info`
---with the content of `dcojmwxi3tl`.
---@param info number
---@param dcojmwxi3tl string Expected format: `<d>:<c>:<o>:<j>:<m>:<w>:<x>:<i>:<3>:<t>:<l>`, with each platform either a Boolean string ("y", "0", etc.) or an empty string
---@return number
local function override(info, dcojmwxi3tl)
	for k, v in pairs(explode(':', dcojmwxi3tl)) do
		if v ~= '' then
			-- for me: a clearer name
			flagWord = v
			-- Normalize string
			if flagWord then
				flagWord = mw.text.trim(flagWord):lower()
			-- Affirmative and negative keywords placed here for reducing redundancy
			local affirmative = { "1", "y", "yes", "on", "t", "true" }
			local negative = { "0", "n", "no", "off", "f", "false" }
			-- Default values
			local matchAffirmative = false
			local matchNegative = false
			-- Check against the lists
			for key, value in pairs(affirmative) do -- For all the values/"items" in the list, do this:
				if value == flagWord then
					matchAffirmative = true
				-- You dont want to add an else, since otherwise only the last item would count.
			-- No need to check this if the above returns true.
			if not matchAffirmative then
				for key, value in pairs(negative) do -- For all the values/"items" in the list, do this:
					if value == flagWord then
						matchNegative = true
					-- You dont want to add an else, since otherwise only the last item would count.
			if matchAffirmative then
				info = bit32.replace(info, 1, k-1)
			elseif matchNegative then
				info = bit32.replace(info, 0, k-1)
	return info

	-- Example to demonstrate the behavior of this function:
	-- Goal: Change "dcom" to "dco3".
	-- info = 23 ("dcom"), dcojmwxi3tl = "::yes:::no::::"
	-- decimal 23 = binary 00000010111
	-- The first "no" is at index 4 in the dcojmwxi3tl string, so
	-- replace the corresponding bit with a 0: 00000010111 -> 00000000111.
	-- The "yes" is at index 8, so replace the bit at index 8
	-- with a 1: 00000000111 -> 00100000111.
	-- The result is info = 263 ("dco3").

---Main function to retrieve exclusivity information.
---@param page string The entity to get the info about.
---@param invert boolean Whether to invert the exclusivity info.
---@param pagenot string The entity whose exclusivity info to subtract from the main one's.
---@param dcojmwxi3tl string Manual exclusivity info to override the fetched one with.
---@return number info An integer that holds the exclusivity information.
local function getInfo(page, invert, pagenot, dcojmwxi3tl)
	local info = 0

	-- A piece of exclusivity information is a set of Boolean values, one
	-- for each platform. This is represented as bits of the `info` integer.
	-- Each platform (Desktop, Console, Old-gen console, Japanese console, Mobile, Windows Phone, New Chinese, Old Chinese
	-- 3DS, tModLoader, tModLoader-Legacy – "dcojmwxi3tl") is assigned one bit, in this order.
	-- This means that, for instance, an `info` value of 1 would represent
	-- Desktop-only exclusivity ("d"):
	-- decimal 1 = binary 00000000001
	--                    lt3ixwmjocd  -> "d"
	-- Similarly, an `info` value of 260 would represent Old-gen and 3DS exclusivity ("o3"):
	-- decimal 260 = binary 00100000100
	--                      lt3ixwmjocd -> "o3"
	-- See Module:Exclusive/data for a quick overview of all values.

	-- This system allows using bitwise operations (https://en.wikipedia.org/wiki/Bitwise_operation)
	-- instead of the formerly used string processing, resulting in much lower script execution times.

	-- get info about page
	if page then
		info = readFromDb(page)
		if invert then
			-- invert dcojmwxi3tl and set j=0 and x=0 (always force-off Japanese console and New Chinese when inverting)
			-- (0x7B7 is 11110110111 in binary, i.e. "dcomwi3tl") (according to Module:Exclusive/data, "dcomwi3tl" would be 1975)
			info = bit32.band(bit32.bnot(info), 0x7B7)
		if pagenot then
			-- exclude some versions, depending on pagenot
			local info_not = readFromDb(pagenot)
			local info_ = info
			if info == 0 then info_ = 0x7B7 end -- $not is used to create a range, e.g. between 1.2 (which by itself returns 0) and 1.3
			info = bit32.band(info_, bit32.bnot(info_not))

		-- The "invert" and "pagenot" functionalities above utilize
		-- bit masking (https://en.wikipedia.org/wiki/Mask_(computing)).
		-- The following example demonstrates the operations:
		-- 1. assume info=787 ("dcm3t", binary 01100010011)
		-- 2. invert:
		--    2a. not(01100010011) = 10011101100 ("ojwxil", the inverse of "dcm3t")
		--    2b. and(10010100100, 10011101100) = 10010100100 ("owil", forced-off "j" and "x")
		-- 3. assume info_not=8 ("o", binary 00000000100)
		--    3a. not(00000000100) = 11111111011
		--    3b. and(10010100100, 11111111011) = 10010100000 ("wil")
		-- An initial exclusivity info of "dcm3t" was inverted to "owil",
		-- then "o" was subtracted from it, resulting in the final
		-- exclusivity information of "wil".

	-- override if needed
	if dcojmwxi3tl == nil or dcojmwxi3tl == '::::::::::' then
		return info
		return override(info, dcojmwxi3tl)

---Return an HTML span tag whose `class` attribute is set
---according to the exclusivity `info`.
---@param info number
---@param _small boolean Whether to add the "s" class, for small icons
---@return string
local function eicons(info, _small)
	local class = _small and 'eico s' or 'eico'
	local hovertext

	-- A "true exclusive" (Japanese console, New Chinese) is set, so simply display that
	-- (true exclusives are always alone or not set at all – "dcj" or "dcx", for instance, doesn't exist)
	if bit32.btest(info, 0x8) and bit32.btest(info, 0x40) then
	elseif bit32.btest(info, 0x8) then
		return '<span class="'..class..' j" title="'..l10n('text_3')..'"></span>';
	elseif bit32.btest(info, 0x40) then
		return '<span class="'..class..' x" title="'..l10n('text_6')..'"></span>';

	-- for each platform of dcom3, add the class and load the hovertext if the platform if set
	-- (e.g. for info=82 ("dm3"), append "i0 i4 i8" to the class and load the
	-- "text_0", "text_4", and "text_8" l10n strings)
	local v = {}
	for i = 0, 10 do
		-- Skip past the true exclusives (3 is "j", 6 is "x")
		if i == 3 or i == 6 then
			-- pass
		elseif bit32.btest(info, 2^i) then
			class = class .. " i" .. i
			v[#v+1] = l10n('text_' .. i)
	local hovertext = mw.text.listToText(v, l10n('list_separator'), l10n('list_conjunction'))
	hovertext = string.format(contentLanguage:convertPlural(#v, l10n('version_plural_forms')), hovertext)
	return '<span class="'..class..'" title="'..hovertext..'"><b></b><i></i></span>';

---Check if the `infoToCheck` integer is a valid number for output.
---It is considered invalid if it represents an empty exclusivity ("")
---or a "full" exclusivity, i.e. all platforms being set, with any combination of true exclusives.
---@param infoToCheck number
---@return boolean
local function infoIsInvalid(infoToCheck)
	-- These hex values are, in order:
	-- A completely null exclusivity
	-- Just Japanese and New Chinese
	-- Everything but Japanese console
	-- Everything but New Chinese
	-- Everything but both
	-- Everything
	return infoToCheck == 0x0 or infoToCheck == 0x48 or infoToCheck == 0x7F7 or infoToCheck == 0x7BF or infoToCheck == 0x7B7 or infoToCheck == 0x7FF

-- main return object
return {

-- for templates; get all exclusive info and set it in dplvars.
-- parameters: $1 = pagename
getInfo = function(frame)
	args_table = frame.args -- cache

	local info = getInfo(getArg(1), getArg('invert'), getArg('pagenot'))
	if infoIsInvalid(info) then
		frame:callParserFunction{ name = '#dplvar:set', args = {
			'ex_d', '',
			'ex_c', '',
			'ex_o', '',
			'ex_j', '',
			'ex_m', '',
			'ex_w', '',
			'ex_x', '',
			'ex_i', '',
			'ex_3', '',
			'ex_t', '',
			'ex_l', '',
			'ex_cached', 'y'
		} }
		frame:callParserFunction{ name = '#dplvar:set', args = {
			'ex_d', bit32.btest(info, 2^0) and 'y' or '',
			'ex_c', bit32.btest(info, 2^1) and 'y' or '',
			'ex_o', bit32.btest(info, 2^2) and 'y' or '',
			'ex_j', bit32.btest(info, 2^3) and 'y' or '',
			'ex_m', bit32.btest(info, 2^4) and 'y' or '',
			'ex_w', bit32.btest(info, 2^5) and 'y' or '',
			'ex_x', bit32.btest(info, 2^6) and 'y' or '',
			'ex_i', bit32.btest(info, 2^7) and 'y' or '',
			'ex_3', bit32.btest(info, 2^8) and 'y' or '',
			'ex_t', bit32.btest(info, 2^9) and 'y' or '',
			'ex_l', bit32.btest(info, 2^10) and 'y' or '',
			'ex_cached', 'y'
		} }

-- for {{eicons}}
eicons = function(frame)
	args_table = parse(frame.args[1])-- cache

	local info = getInfo(getArg('page'), getArg('invert'), getArg('pagenot'), getArg('dcojmwxi3tl'))
	if infoIsInvalid(info) then
		return frame:expandTemplate{ title = 'error', args = { l10n('eicons_error_text'), l10n('eicons_error_cate'), from = 'Eicons' } }
	lang = getArg('lang') -- set lang for l10n
	return eicons(info, getArg('small'))

-- simplified version of the eicons function above, for other modules such as Module:Item
simpleEicons = function(page, language, small)
	local info = getInfo(page)
	if infoIsInvalid(info) then
		return ''
	lang = language -- set lang for l10n
	return eicons(info, small)