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

---Override the exclusivity information in `info` ---with the content of `jdcom3stlxi`. ---@param info number ---@param jdcom3stlxi string Expected format: `:::::<3>: ::::`, with each platform either a Boolean string ("y", "0", etc.) or an empty string ---@return number local function override(info, jdcom3stlxi) for k, v in pairs(explode(':', jdcom3stlxi)) do		if v ~= '' then if v == '1' or v == 'y' or v == 'yes' then info = bit32.replace(info, 1, k-1) elseif v == '0' or v == 'n' or v == 'no' 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 = 30 ("dcom"), jdcom3stlxi = "::::no:yes::" -- decimal 30 = binary 00000011110 -- The first "no" is at index 4 in the jdcom3stlxi string, so -- replace the corresponding bit with a 0: 00000011110 -> 00000001110. -- The "yes" is at index 5, so replace the bit at index 5 -- with a 1: 00000001110 -> 00000101110. -- The result is info = 46 ("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 jdcom3stlxi 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, jdcom3stlxi) 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 (Japanese console, Desktop, Console, Old-gen console, Mobile, 3DS,	-- Switch, TModLoader, TModLoader-Legacy, New Chinese, Old Chinese – "jdcom3stlxi") is assigned one bit, in this order. -- This means that, for instance, an `info` value of 2 would represent -- Desktop-only exclusivity ("d"): -- decimal 2 = binary 00000000010 --                    ixlts3mocdj  -> "d" -- Similarly, an `info` value of 40 would represent Old-gen and 3DS exclusivity ("o3"): -- decimal 40 = binary 00000101000 --                    ixlts3mocdj -> "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 jdcom3stlxi and set j=0 (always force-off Japanese console when inverting) -- (0x7FE is 1111111110 in binary, i.e. "dcom3stlxi") (according to Module:Exclusive/data, "dcom3stlxi" would be 1022) info = bit32.band(bit32.bnot(info), 0x7FE) end if pagenot then -- exclude some versions, depending on pagenot local info_not = readFromDb(pagenot) local info_ = info if info == 0 then info_ = 0x7FE 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=214 ("dcmst", binary 00011010110) -- 2. invert: --   2a. not(00011010110) = 00000101001 ("jo3", the inverse of "dcmst") --   2b. and(00000101001, 00011111110) = 00000101000 ("o3", forced-off "j") -- 3. assume info_not=8 ("o", binary 00000001000) --   3a. not(00000001000) = 00011110111 --   3b. and(00000101000, 00011110111) = 00000100000 ("3") -- An initial exclusivity info of "dcmst" was inverted to "o3", -- then "o" was subtracted from it, resulting in the final -- exclusivity information of "3". end

-- override if needed if jdcom3stlxi == nil or jdcom3stlxi == ':::::::' then return info else return override(info, jdcom3stlxi) 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

if bit32.btest(info, 0x01) then -- Japanese console is set, so simply display that -- ("j" is always alone or not set at all – "dcj", for instance, doesn't exist) return ' '; end

-- for each platform of dcom3, add the class and load the hovertext if the platform if set -- (e.g. for info=50 ("dm3"), append "i1 i4 i5" to the class and load the	-- "text_1", "text_4", and "text_5" l10n strings) local v = {} for i = 1, 10 do -- 0 is "j" if 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 `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 ("dcom3stlxi" or "jdcom3stlxi"). ---@param infoToCheck number ---@return boolean local function infoIsInvalid(infoToCheck) return infoToCheck == 0x000 or infoToCheck == 0x7FE 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_j', '', 'ex_d', '', 'ex_c', '', 'ex_o', '', 'ex_m', '', 'ex_3', '', 'ex_s', '', 'ex_t', '', 'ex_l', '', 'ex_x', '', 'ex_i', '', 'ex_cached', 'y'		} } else frame:callParserFunction{ name = '#dplvar:set', args = { 'ex_j', bit32.btest(info, 2^0) and 'y' or '', 'ex_d', bit32.btest(info, 2^1) and 'y' or '', 'ex_c', bit32.btest(info, 2^2) and 'y' or '', 'ex_o', bit32.btest(info, 2^3) and 'y' or '', 'ex_m', bit32.btest(info, 2^4) and 'y' or '', 'ex_3', bit32.btest(info, 2^5) and 'y' or '', 'ex_s', bit32.btest(info, 2^6) and 'y' or '', 'ex_t', bit32.btest(info, 2^7) and 'y' or '', 'ex_l', bit32.btest(info, 2^8) and 'y' or '', 'ex_x', bit32.btest(info, 2^9) and 'y' or '', 'ex_i', 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('jdcom3stlxi')) 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,

}