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 `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 -- 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) 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 `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 -- Everything but Japanese console -- Everything but New Chinese -- Everything return infoToCheck == 0x000 or infoToCheck == 0x7F7 or infoToCheck == 0x7BF 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 = 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,

}