Module:Exclusive

---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 1975 -- if we have no information on the page, assume it is available in all versions end

---Override the exclusivity information in `info` ---with the content of `dcojmwxi3tl`. ---@param info number ---@param dcojmwxi3tl string Expected format: `::::::::<3>::`, 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 if v == '1' or v == 'y' or v == 'yes' or v == 'on' or v == 't' or v == 'true' then info = bit32.replace(info, 1, k-1) elseif v == '0' or v == 'n' or v == 'no' or v == 'off' or v == 'f' or v == 'false' 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 both are set, infoIsInvalid will catch that later. if bit32.btest(info, 0x8) then return ' '; elseif bit32.btest(info, 0x40) then return ' '; 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 '  '; end

-- Check if the output is nonsensical. ---@param infoToCheck number ---@return boolean local function infoIsInvalid(infoToCheck) -- Both true exclusives are enabled at once. if bit32.btest(infoToCheck, 0x8) and bit32.btest(infoToCheck, 0x40) then return true end end

-- Check if all non-true exclusives are enabled. ---@param infoToCheck number ---@return boolean local function isFullExclusivity(infoToCheck) if bit32.btest(infoToCheck, 0x1) and bit32.btest(infoToCheck, 0x2) and bit32.btest(infoToCheck, 0x4) and bit32.btest(infoToCheck, 0x10) and bit32.btest(infoToCheck, 0x20) and bit32.btest(infoToCheck, 0x100) and bit32.btest(infoToCheck, 0x200) and bit32.btest(infoToCheck, 0x400) then return true else return false end 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 = function(frame) args_table = parse(frame.args[1])-- cache

local info = getInfo(getArg('page'), getArg('invert'), getArg('pagenot'), getArg('dcojmwxi3tl'))

-- An empty exclusivity means no icons should be rendered. This isn't supposed to happen, obviously. if info == 0x0 then return frame:expandTemplate{ title = 'error', args = { l10n('eicons_empty_text'), l10n('eicons_empty_cate'), from = 'Eicons' } } end -- Every non-exclusive is enabled. This means that the template is completely unnecessary. if isFullExclusivity(info) then return frame:expandTemplate{ title = 'error', args = { l10n('eicons_full_text'), l10n('eicons_full_cate'), from = 'Eicons' } } end -- Info is invalid in some other way. Use generic error message. 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 info == 0x0 or isFullExclusivity(info) or infoIsInvalid(info) then return '' end lang = language -- set lang for l10n return eicons(info, small) end,

}