This module is the core of the dynamic platform exclusivity system.
The basic function to invoke from templates is
, which queries the exclusivity information database and stores the result to a set of dplvars. These are named in the format getInfo
ex_<platform>
, e.g.
for Desktop, and they are either empty or set to ex_d
. The function does not produce any output.
y
The two other functions,
and eicons
, return the HTML code for the icons denoting platform exclusivity. The first function is intended to be used only by the simpleEicons
{{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]
else
return l10n_data['en'][key]
end
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 = trim(args_table[key] or '')
return (value ~= '') and value or nil
end
---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
end
return args
end
---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
end
arr[#arr + 1] = string.sub(str,pos) -- Attach chars right of last divider
return arr
end
---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
end
---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()
end
-- 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
end
-- You dont want to add an else, since otherwise only the last item would count.
end
-- 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
end
-- You dont want to add an else, since otherwise only the last item would count.
end
end
if matchAffirmative then
info = bit32.replace(info, 1, k-1)
elseif matchNegative then
info = bit32.replace(info, 0, k-1)
end
end
end
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").
end
---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)
end
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))
end
-- 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".
end
-- override if needed
if dcojmwxi3tl == nil or dcojmwxi3tl == '::::::::::' then
return info
else
return override(info, dcojmwxi3tl)
end
end
---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
return
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>';
end
-- 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)
end
end
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>';
end
---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
end
-----------------------------------------------------------------
-- 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'
} }
else
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'
} }
end
end,
-- 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' } }
end
lang = getArg('lang') -- set lang for l10n
return eicons(info, getArg('small'))
end,
-- 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 ''
end
lang = language -- set lang for l10n
return eicons(info, small)
end,
}