mNo edit summary |
mNo edit summary |
||
(648 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} |
local p = {} |
||
local mathOps = require("Module:MathOps") |
local mathOps = require("Module:MathOps") |
||
+ | local str = require("Module:Strings") |
||
local frame = mw.getCurrentFrame() |
local frame = mw.getCurrentFrame() |
||
local _brigtnessTreshold = 0.36 |
local _brigtnessTreshold = 0.36 |
||
+ | local _isValidFileNameCounter = 0 |
||
+ | local _colorOrder = {2, 6, 8, 4, 3, 10, 11, 12, 14, 15, 21, 22, 25, 26, 9, 27, 28, 29, 13, 31, 32} |
||
+ | |||
+ | local strings = { |
||
+ | --genders, not currently used |
||
+ | male = "Male", |
||
+ | female = "Female", |
||
+ | transmale = "Trans Male", |
||
+ | transfemale = "Trans Female", |
||
+ | nonhuman = "Not applicable (not human)", |
||
+ | undefined = "Undefined", |
||
+ | |||
+ | --charNotFound = "Character not found!", |
||
+ | patch = "Patch", |
||
+ | tabberHeaderNotFound = "Header not found", |
||
+ | category = "Category", --used for [[Category:...]] |
||
+ | boxTypeNotFound = b("Dynamic box is missing type!"), |
||
+ | notSpecified = "notSpecified", --used in IconLink for assemble machine function |
||
+ | clrTestString = "An extremely long string that serves as a test string for automatic and ordered coloured text that colour-highlights important values on our beautiful Dead by Daylight wiki. For this testing purposes we consider only 3-characters or longer words", |
||
+ | notEnoughColors = "no more colours in order", |
||
+ | } |
||
+ | p.strings = strings |
||
+ | |||
+ | local months = { |
||
+ | January = "January", |
||
+ | February = "February", |
||
+ | March = "March", |
||
+ | April = "April", |
||
+ | May = "May", |
||
+ | June = "June", |
||
+ | July = "July", |
||
+ | August = "August", |
||
+ | September = "September", |
||
+ | October = "October", |
||
+ | November = "November", |
||
+ | December = "December" |
||
+ | } |
||
+ | |||
+ | local days = { |
||
+ | Monday = "Monday", |
||
+ | Tuesday = "Tuesday", |
||
+ | Wednesday = "Wednesday", |
||
+ | Thursday = "Thursday", |
||
+ | Friday = "Friday", |
||
+ | Saturday = "Saturday", |
||
+ | Sunday = "Sunday" |
||
+ | } |
||
+ | ---------------------------------------------- |
||
+ | |||
+ | p.timeFormat1 = "%d.%m.%Y" -- 5.9.2022 |
||
+ | p.timeContants = { |
||
+ | second = 1, |
||
+ | minute = 60, --in seconds |
||
+ | hour = 3600, |
||
+ | day = 86400, |
||
+ | week = 604800 |
||
+ | } |
||
+ | local timeContants = p.timeContants |
||
+ | |||
+ | --Bridge to Language Module, as more appropriate place to have this function |
||
+ | function p.lang() |
||
+ | return require("Module:Languages").lang() |
||
+ | end |
||
+ | |||
+ | --If params will be passed {...} then index should equals 0, so this list will be passeb back: ((index == 0 and params.args) or params) |
||
+ | function p.resolveParameter(param, index, returnCanBeNil) |
||
+ | local retArg |
||
+ | if type(param) == "table" then retArg = ((index == 0 and param.args) or (param.args and param.args[(index or 1)] and p.replaceSpecialCharacters(param.args[(index or 1)]))) or nil end --if parameter is passed from wiki, not other function |
||
+ | if retArg == nil and type(param) == "table" and param.args ~= nil and next(param.args) == nil and not returnCanBeNil then retArg = getParamOrPageName() end --param.args ~= nil and next(param.args): this means that params.args is not nil but empty table |
||
+ | if retArg == nil and not returnCanBeNil then retArg = getParamOrPageName(param, index, false) end --if parameter was passed directly or not at all |
||
+ | if retArg ~= nil and type(param) == "string" then retArg = p.replaceSpecialCharacters(retArg) end --final processing |
||
+ | return retArg |
||
+ | end |
||
+ | |||
+ | function getParamOrPageName(param, index, returnCanBeNil) |
||
+ | if param and |
||
+ | (type(param) ~= types.table or |
||
+ | (type(param) == types.table and not param.args)) |
||
+ | then |
||
+ | return param |
||
+ | else |
||
+ | local pageName = pageName or mw.title.getCurrentTitle().text |
||
+ | local pageParts = string.split(pageName, '/') -- remove page suffixes such as language codes |
||
+ | if #pageParts > 1 then |
||
+ | _unofficialLang = pageParts[#pageParts] |
||
+ | return pageParts[#pageParts - 1] --take second to last part of page name |
||
+ | end |
||
+ | return pageParts[1] --if there is no other part than the name itself |
||
+ | end |
||
+ | end |
||
+ | |||
+ | --translates string containing #1# #2# ... to individual strings (resp. words) contained in dyntable |
||
+ | function p.getDynamicString(dynTable, templateString) |
||
+ | --dynTable = {"Testing Chapter", nil, "DLC Str World", nil, 999}; |
||
+ | --templateString = "Retracted #1# #2# #3# #4# #5# #6# #7# #8#" |
||
+ | local skipList = {skip = {}, spans = {}} |
||
+ | local spanRegex = "(<span .-</span>)" --mostly clr case |
||
+ | local spanSkipRegex = "&&span" --it needs to use a DIFFERENT symbol than hashtag (#) as the style of spans can contain one due to colorhex colour value |
||
+ | local skipRegex = "#(.-)#" --skip anything inside hashtags |
||
+ | if type(dynTable) == types.string then --is parameter is string then just split it |
||
+ | dynTable = string.split(dynTable) |
||
+ | elseif type(dynTable) == types.table then |
||
+ | local tmp = {} |
||
+ | local skipCounter = 1 |
||
+ | local spanCounter = 1 |
||
+ | for i, el in pairs(dynTable) do |
||
+ | if el:find(spanRegex) then --if you find any spans to skip then ... |
||
+ | for m in el:gmatch(spanRegex) do --let's find spans (and pull them out) to skip them with splitting by "space" |
||
+ | table.insert(skipList.spans, m) |
||
+ | el = el:gsub(m, spanSkipRegex .. spanCounter) |
||
+ | spanCounter = spanCounter + 1 |
||
+ | end |
||
+ | end |
||
+ | |||
+ | --finding any strings DOES NOT need to be first as span regex WILL NOT create elements matching this regex |
||
+ | if el:find(skipRegex) then --if you find any string to skip then ... |
||
+ | for m in el:gmatch(skipRegex) do |
||
+ | table.insert(skipList.skip, m) |
||
+ | el = el:gsub(skip(m:gsub('%W','%%%1')), "#skip" .. skipCounter) --We have to escape special characters as the string is being used as a pattern and for an instance "[[ ]]" are magic chars for LUA patterns |
||
+ | skipCounter = skipCounter + 1 |
||
+ | end |
||
+ | end |
||
+ | |||
+ | table.insert(tmp, string.split(tostring(el))) --if parameter is table, split all strings inside |
||
+ | end |
||
+ | dynTable = table.flatten(tmp) --format from table.insert is {{"splitted", "string"}, {"splitted", "another", "string"}}, so it's needed to be flattened |
||
+ | --mw.log(mw.dumpObject(dynTable)) |
||
+ | end |
||
+ | |||
+ | if templateString then |
||
+ | for m in templateString:gmatch("#(%d+)#") do --replace all #1# occurences |
||
+ | local currentRegexString = "#" .. tonumber(m) .. "#" |
||
+ | if tonumber(m) <= #dynTable then |
||
+ | templateString = templateString:gsub(currentRegexString, dynTable[tonumber(m)]) |
||
+ | else |
||
+ | templateString = templateString:gsub(currentRegexString, cstr.empty) --remove all remaining marks |
||
+ | end |
||
+ | end |
||
+ | else |
||
+ | templateString = table.join(dynTable, space) |
||
+ | end |
||
+ | |||
+ | templateString = string.trim(templateString) |
||
+ | |||
+ | --lg(templateString) |
||
+ | for m in templateString:gmatch("#skip(%d)") do --now we put spans back into string |
||
+ | local currentRegexString = "#skip" .. tonumber(m) |
||
+ | templateString = templateString:gsub(currentRegexString, skipList.skip[tonumber(m)]) |
||
+ | end |
||
+ | |||
+ | for m in templateString:gmatch(spanSkipRegex .. "(%d)") do --now we put spans back into string |
||
+ | local currentRegexString = spanSkipRegex .. tonumber(m) |
||
+ | templateString = templateString:gsub(currentRegexString, skipList.spans[tonumber(m)]) |
||
+ | end |
||
+ | |||
+ | --mw.log(templateString) |
||
+ | return templateString |
||
+ | end |
||
+ | -------------------------------------------------------------------------------- |
||
function p.getCount(subject) |
function p.getCount(subject) |
||
local list |
local list |
||
+ | subject = p.resolveParameter(subject) |
||
− | |||
--if you have another list just add it into a list then call appropriate function |
--if you have another list just add it into a list then call appropriate function |
||
− | if subject == "map" then |
+ | if subject == "map" then return p.getMapsCount() |
elseif subject == "realm" then list = realms |
elseif subject == "realm" then list = realms |
||
elseif subject == "killer" then list = killers |
elseif subject == "killer" then list = killers |
||
elseif subject == "survivor" then list = survivors |
elseif subject == "survivor" then list = survivors |
||
elseif subject == "dlc" then list = dlcs |
elseif subject == "dlc" then list = dlcs |
||
− | elseif subject == "chapter" then return getCountDlcType(1) |
+ | elseif subject == "chapter" then return p.getCountDlcType(1) |
− | elseif subject == "paragraph" then return getCountDlcType(2) |
+ | elseif subject == "paragraph" then return p.getCountDlcType(2) |
− | elseif subject == "clothing" then return getCountDlcType(3) |
+ | elseif subject == "clothing" then return p.getCountDlcType(3) |
− | elseif subject == "ost" then return getCountDlcType(4) |
+ | elseif subject == "ost" then return p.getCountDlcType(4) |
− | elseif subject == "character" then return getCountDlcType(5) |
+ | elseif subject == "character" then return p.getCountDlcType(5) |
elseif subject == "ccy" then return getCountCCY(true) |
elseif subject == "ccy" then return getCountCCY(true) |
||
elseif subject == "ccy-gc" then return getCountCCY(true) --redundant option to keep convention |
elseif subject == "ccy-gc" then return getCountCCY(true) --redundant option to keep convention |
||
elseif subject == "ccy-rc" then return getCountCCY(false) |
elseif subject == "ccy-rc" then return getCountCCY(false) |
||
+ | elseif subject == "killerPerk" then return getPerksCount('K') |
||
+ | elseif subject == "survPerk" then return getPerksCount('S') |
||
else return 0 |
else return 0 |
||
end |
end |
||
local x = 0 |
local x = 0 |
||
− | for _, item in ipairs(list) do if item.skip then x = x + 1 end end |
+ | for _, item in ipairs(list) do if item.skip or item.retired then x = x + 1 end end |
return #list - x |
return #list - x |
||
end |
end |
||
− | function |
+ | function p.getMapsCount() |
+ | local data = require("Module:Datatable") |
||
+ | local result = 0 |
||
+ | |||
+ | for _, map in ipairs(data.maps) do |
||
+ | if not (map.skip or map.retired) then |
||
+ | if map.variations then |
||
+ | for _, var in ipairs(map.variations) do |
||
+ | if not (map.skip or map.retired) then |
||
+ | result = result + 1 |
||
+ | end |
||
+ | end |
||
+ | else |
||
+ | result = result + 1 |
||
+ | end |
||
+ | end |
||
+ | end |
||
+ | |||
+ | return result |
||
+ | end |
||
+ | |||
+ | function p.getCountDlcType(type) |
||
local count = 0 |
local count = 0 |
||
− | for |
+ | for _, dlc in ipairs(dlcs) do |
if dlc.category == type and not dlc.skip then count = count + 1 end |
if dlc.category == type and not dlc.skip then count = count + 1 end |
||
end |
end |
||
+ | return count |
||
+ | end |
||
+ | |||
+ | function getPerksCount(charType) |
||
+ | local perks = mw.loadData("Module:Datatable/Loadout" .. p.lang()).perks |
||
+ | local count = 0 |
||
+ | |||
+ | for _, perk in ipairs(perks) do |
||
+ | if perk.charType == charType and not perk.unused then count = count + 1 end |
||
+ | end |
||
+ | |||
return count |
return count |
||
end |
end |
||
Line 47: | Line 241: | ||
return i |
return i |
||
+ | end |
||
+ | |||
+ | function isRealCcy(ccyId) |
||
+ | local data = require("Module:Datatable" .. p.lang()) |
||
+ | for _, currency in ipairs(data.ccy) do |
||
+ | if currency.id == ccyId then return not currency.gc end |
||
+ | end |
||
+ | return false |
||
+ | end |
||
+ | |||
+ | function p.getCcyById(id) |
||
+ | for _, currCcy in ipairs(ccy) do |
||
+ | if currCcy.id == id then return currCcy end |
||
+ | end |
||
+ | return nil |
||
+ | end |
||
+ | |||
+ | function p.getMaxId(tab) |
||
+ | result = 0 |
||
+ | for _, item in ipairs(tab) do |
||
+ | result = (item.id > result and item.id) or result |
||
+ | end |
||
+ | return result |
||
end |
end |
||
-- Function allowing for consistent treatment of boolean-like wikitext input. |
-- Function allowing for consistent treatment of boolean-like wikitext input. |
||
-- It works similarly to the template {{yesno}}. |
-- It works similarly to the template {{yesno}}. |
||
− | |||
function p.bool(val, default) |
function p.bool(val, default) |
||
-- If your wiki uses non-ascii characters for any of "yes", "no", etc., you |
-- If your wiki uses non-ascii characters for any of "yes", "no", etc., you |
||
Line 98: | Line 314: | ||
--NEG $22,333,444.563 |
--NEG $22,333,444.563 |
||
+ | function p.formatNum(amount, decimal, prefix, neg_prefix) |
||
+ | return p.format_num(amount, decimal, prefix, neg_prefix) |
||
+ | end |
||
function p.format_num(amount, decimal, prefix, neg_prefix) |
function p.format_num(amount, decimal, prefix, neg_prefix) |
||
local str_amount, formatted, famount, remain |
local str_amount, formatted, famount, remain |
||
Line 146: | Line 365: | ||
--Converting Arabic numbers to Roman |
--Converting Arabic numbers to Roman |
||
− | function |
+ | function p.toRomanNumerals(s) |
local numbers = { 1, 5, 10, 50, 100, 500, 1000 } |
local numbers = { 1, 5, 10, 50, 100, 500, 1000 } |
||
local chars = { "I", "V", "X", "L", "C", "D", "M" } |
local chars = { "I", "V", "X", "L", "C", "D", "M" } |
||
s = tonumber(s) |
s = tonumber(s) |
||
− | if not s or s ~= s then |
+ | if not s or s ~= s then return false end |
− | if s == math.huge then |
+ | if s == math.huge then return false end |
s = math.floor(s) |
s = math.floor(s) |
||
if s <= 0 then return s end |
if s <= 0 then return s end |
||
− | local ret = |
+ | local ret = cstr.empty |
for i = #numbers, 1, -1 do |
for i = #numbers, 1, -1 do |
||
local num = numbers[i] |
local num = numbers[i] |
||
Line 202: | Line 421: | ||
local result |
local result |
||
if string.match(romanNumber, "[MDCLXVI]+$") then --finding ONLY Roman letters in last "word" |
if string.match(romanNumber, "[MDCLXVI]+$") then --finding ONLY Roman letters in last "word" |
||
− | --Cowshed |
||
− | mw.log(romanNumber) |
||
result = str |
result = str |
||
− | --result = string.sub(str, 1, index - 1) .. ToNumeral(romanNumber) |
||
else |
else |
||
− | mw.log("Non-Roman form") |
||
result = str |
result = str |
||
end |
end |
||
Line 214: | Line 429: | ||
end |
end |
||
− | function p.CapitalizeName(str) |
+ | function p.capitalizeName(str) return p.CapitalizeName(str) end |
+ | function p.CapitalizeName(str) --deprecated name |
||
− | return string.gsub(" "..str, "%W%l", string.upper):sub(2) |
||
+ | --mw.log(string.gsub(" "..str, "[%s%-]%a", string.upper):sub(2)) |
||
+ | --return string.gsub(" "..str, "[%s%-]%a", string.upper):sub(2) --original version |
||
+ | return string.gsub(" "..str:lower(), "[%s%-]%a", string.upper):sub(2) |
||
end |
end |
||
− | function p.FirstLetterLower(str) |
+ | function p.firstLetterLower(str) return p.FirstLetterLower(str) end |
+ | function p.FirstLetterLower(str) --deprecated name |
||
return str:sub(1, 1):lower() .. str:sub(2) |
return str:sub(1, 1):lower() .. str:sub(2) |
||
end |
end |
||
− | function p. |
+ | function p.firstLetterUpper(str) return p.FirstLetterUpper(str) end |
+ | function p.FirstLetterUpper(str) --deprecated name |
||
− | str = string.gsub(str, "[']", "") |
||
+ | return str:sub(1,1):upper() .. str:sub(2) |
||
− | str = string.gsub(str, "[®]", "") |
||
+ | end |
||
− | str = string.gsub(str, "[™]", "") |
||
+ | |||
− | str = string.gsub(str, "[:]", "") |
||
+ | --[[function p.fixDiacritics(str) |
||
− | str = string.gsub(str, "[!]", "") --perks |
||
+ | local letterSequences = { |
||
− | str = string.gsub(str, "[&]", "And") --probably can be changed to lower: "and" |
||
+ | ["195"] = {fix = "â", sequence = {"162"}} |
||
− | |||
+ | } |
||
+ | local fixedSeqsOffset = 0 |
||
+ | |||
+ | local currentLen = #str + fixedSeqsOffset |
||
+ | local i = 1 |
||
+ | while i <= currentLen do |
||
+ | local ascii = tostring(string.byte(str:sub(i,i)) or 0) |
||
+ | for startSequence, seqData in pairs(letterSequences) do |
||
+ | if ascii == startSequence then |
||
+ | local foundSequence = true |
||
+ | for j, seqCode in ipairs(seqData.sequence) do |
||
+ | local seqAscii = tostring(string.byte(str:sub(i+j,i+j))) |
||
+ | if seqAscii ~= seqCode then |
||
+ | foundSequence = false |
||
+ | break |
||
+ | end |
||
+ | end |
||
+ | |||
+ | if foundSequence then |
||
+ | fixedSeqsOffset = i - (i + #seqData.sequence) |
||
+ | currentLen = #str + fixedSeqsOffset |
||
+ | -- #seqData.sequence + 1 = number of ascii codes that needs to be replaced, the sequence list + the initial ascii code, i.e. the key value from letterSequences |
||
+ | str = str:sub(1, i - 1) .. seqData.fix .. str:sub(i + #seqData.sequence + 1) |
||
+ | end |
||
+ | end |
||
+ | end |
||
+ | i = i + 1 |
||
+ | end |
||
+ | return str |
||
+ | end]] |
||
+ | |||
+ | function p.RemoveSpecialCharacters(str, full, replaceDiacritics) |
||
+ | str = string.gsub(str, "'", "") |
||
+ | str = string.gsub(str, "®", "") |
||
+ | str = string.gsub(str, "™", "") |
||
+ | str = string.gsub(str, ":", "") |
||
+ | str = string.gsub(str, "!", "") --perks |
||
+ | str = string.gsub(str, "?", "") --achievements |
||
+ | str = string.gsub(str, "/", "") --achievements |
||
+ | str = string.gsub(str, "%&", "And") --probably can be changed to lower: "and" |
||
+ | |||
+ | if replaceDiacritics then |
||
+ | str = p.replaceDiacritics(str, full) |
||
+ | end |
||
+ | |||
+ | return str |
||
+ | end |
||
+ | |||
+ | function p.replaceDiacritics(str, full) |
||
--Diacritics |
--Diacritics |
||
+ | specialLettersLight = { |
||
+ | ["A"] = {"À", "Á", "Â", "Ã", "Ä"}, |
||
+ | ["a"] = {"à", "á", "â", "ã", "ä"}, |
||
+ | ["e"] = {"è", "é", "ê", "ë", "ě"}, |
||
+ | ["O"] = {"Ò", "Ó", "Ô", "Õ", "Ö"}, |
||
+ | ["o"] = {"ò", "ó", "ô", "õ", "ö"} |
||
+ | } |
||
+ | specialLetters = { |
||
+ | ["A"] = {"À", "Á", "Â", "Ã", "Ä"}, |
||
+ | ["a"] = {"à", "á", "â", "ã", "ä"}, |
||
+ | ["E"] = {"È", "É", "Ê", "Ë", "Ě"}, |
||
+ | ["e"] = {"è", "é", "ê", "ë", "ě"}, |
||
+ | ["I"] = {"Ì", "Í", "Î", "Ñ", "Ï"}, |
||
+ | ["i"] = {"ì", "í", "î", "ñ", "ï"}, |
||
+ | ["O"] = {"Ò", "Ó", "Ô", "Õ", "Ö"}, |
||
+ | ["o"] = {"ò", "ó", "ô", "õ", "ö"}, |
||
+ | ["U"] = {"Ù", "Ú", "Û", "Ů", "Ü"}, |
||
+ | ["u"] = {"ù", "ú", "û", "ů", "ü"}, |
||
+ | ["Y"] = { "Ý", "Ÿ"}, |
||
+ | ["y"] = { "ý", "ÿ"} |
||
+ | } |
||
+ | --Originally it was grouped by set [ÀÁ], however there is some sort of bug there that makes it not working |
||
--str = string.gsub(str, "[ÁÂ]", "A") |
--str = string.gsub(str, "[ÁÂ]", "A") |
||
+ | --if debugRun then mw.log(mw.dumpObject(specialLetters)) end |
||
− | str = string.gsub(str, "[áâ]", "a") |
||
+ | for letter, row in pairs(((full and specialLetters) or specialLettersLight)) do |
||
− | str = string.gsub(str, "[ÉĚ]", "E") |
||
+ | for _, special in ipairs(row) do |
||
− | str = string.gsub(str, "[éě]", "e") |
||
− | + | str = string.replace(str, special, letter) |
|
+ | end |
||
− | str = string.gsub(str, "[í]", "i") |
||
+ | end |
||
− | str = string.gsub(str, "[Ó]", "O") |
||
− | str = string.gsub(str, "[ó]", "o") |
||
− | str = string.gsub(str, "[ÚŮ]", "U") |
||
− | str = string.gsub(str, "[úů]", "u") |
||
− | str = string.gsub(str, "[Ý]", "Y") |
||
− | str = string.gsub(str, "[ý]", "y") |
||
return str |
return str |
||
end |
end |
||
− | function p. |
+ | function p.replaceSpecialCharacters(name) |
+ | local charList = { |
||
+ | ["'"] = ''', |
||
+ | ["&"] = '&' |
||
+ | } |
||
+ | if name == nil then error("Name parameter is empty, but it shouldnt?") end |
||
+ | for repl, sChar in pairs(charList) do |
||
+ | name = string.gsub(tostring(name), sChar, repl) |
||
+ | end |
||
+ | return name |
||
+ | end |
||
+ | |||
+ | function p.pageExists(name) |
||
+ | return (not name == cstr.empty) or mw.title.new(name).exists |
||
+ | end |
||
+ | |||
+ | function p.isValidFileName(name, extension) |
||
+ | extension = extension or "png" |
||
name = p.RemoveSpecialCharacters(name) |
name = p.RemoveSpecialCharacters(name) |
||
+ | lg("Validation check, Name: " .. name) |
||
− | return not (name == "" or not mw.title.new("File:" .. name .. ".png").exists) |
||
+ | _isValidFileNameCounter = _isValidFileNameCounter + 1 |
||
+ | --mw.log("Counter:" .. tostring(_isValidFileNameCounter) .. ' - ' .. cstr.media .. name .. dot .. extension) |
||
+ | return not (name == cstr.empty or not mw.title.new(cstr.media .. name .. dot .. extension).exists) |
||
end |
end |
||
− | function p.resolveFileName(str, keepSpaces) |
+ | function p.resolveFileName(str, keepSpaces, removeDiacritics) |
keepSpaces = keepSpaces or false |
keepSpaces = keepSpaces or false |
||
local result = "" |
local result = "" |
||
Line 257: | Line 562: | ||
result = string.lower(str) |
result = string.lower(str) |
||
--mw.log(result) |
--mw.log(result) |
||
− | result = p.RemoveSpecialCharacters(result) |
+ | result = p.RemoveSpecialCharacters(result, keepSpaces, removeDiacritics) |
--mw.log(result) |
--mw.log(result) |
||
− | result = p. |
+ | result = p.capitalizeName(result) |
if not keepSpaces then |
if not keepSpaces then |
||
result = string.gsub(result, "[ ]", "") |
result = string.gsub(result, "[ ]", "") |
||
Line 265: | Line 570: | ||
--In future if there will be needed replace charactere such as "é" just add another substitution |
--In future if there will be needed replace charactere such as "é" just add another substitution |
||
+ | --mw.log(result) |
||
return result |
return result |
||
end |
end |
||
function p.resolveImageName(name) |
function p.resolveImageName(name) |
||
− | if mw.title.new( |
+ | if mw.title.new(cstr.media .. name .. dot .. cstr.png).exists then return name .. dot .. cstr.png end |
− | if mw.title.new( |
+ | if mw.title.new(cstr.media .. name .. dot .. cstr.jpg).exists then return name .. dot .. cstr.jpg end |
+ | if mw.title.new(cstr.media .. name .. dot .. cstr.gif).exists then return name .. dot .. cstr.gif end |
||
− | return name .. ".png" |
||
+ | return name .. dot .. cstr.png |
||
end |
end |
||
Line 278: | Line 585: | ||
end |
end |
||
− | function p. |
+ | function p.IconLink(icon, pageLink, displayText, background) |
+ | local result = cstr.empty |
||
− | return frame:expandTemplate{title = "clr", args = {color, text}} |
||
+ | background = background or p.resolveParameter(icon, "bg", true) or p.resolveParameter(icon, 4, true) |
||
+ | background = p.bool(background == nil or background) |
||
+ | displayText = displayText or p.resolveParameter(icon, 3, true) |
||
+ | pageLink = pageLink or p.resolveParameter(icon, 2, true) |
||
+ | |||
+ | local linkless = p.resolveParameter(icon, "linkless", true) or pageLink == "linkless" --linkless as a second parameter should be deprecated |
||
+ | icon = p.resolveParameter(icon) |
||
+ | local iconObject, pluralType = p.getIconObject(icon) |
||
+ | if pluralType then --if automatic plural is detected, then links should redirect to singular form |
||
+ | pageLink = pageLink or iconObject.name |
||
+ | displayText = icon --since automatic plurals don't imitate 3-parameters scenario we have to set the display text to be same as the first parameter (icon) |
||
+ | end |
||
+ | local iconFile = iconObject.iconFile |
||
+ | local filename = cstr.file .. iconFile .. tl .. "link=" .. (pageLink or icon) |
||
+ | local text = (displayText and pageLink and pageLink .. tl .. displayText) or pageLink or icon --cstr.empty |
||
+ | --local boxDesc = cstr.empty |
||
+ | |||
+ | if pageLink == "img" then |
||
+ | text = cstr.empty |
||
+ | filename = cstr.file .. iconFile .. tl .. "link=" .. icon |
||
+ | end |
||
+ | if not linkless and text ~= cstr.empty then |
||
+ | text = link(text) |
||
+ | --boxDesc = getBoxDescription(pageLink or icon) //hover box, currently disabled |
||
+ | elseif linkless or pageLink == "linkless" then |
||
+ | text = (displayText or icon) |
||
+ | end |
||
+ | |||
+ | local imageFile |
||
+ | if background and iconObject.assembly then |
||
+ | local loLogic = require("Module:Loadout" .. p.lang()) |
||
+ | --this needs to (should) be done sophisticated way |
||
+ | local imgType = |
||
+ | (iconObject.iconFile:lower():find("addon") and "addon") or |
||
+ | (iconObject.iconFile:lower():find("items") and "item") or |
||
+ | (iconObject.iconFile:lower():find("favors") and "offering") or |
||
+ | (iconObject.iconFile:lower():find("powers") and "power") or |
||
+ | (iconObject.iconFile:lower():find("perks") and "perk") or |
||
+ | strings.notSpecified |
||
+ | --if iconObject.iconFile:lower():find("favors") then imgType = "offering" end |
||
+ | local loObj = (loLogic["get" .. p.capitalizeName(imgType) .. "ByName"] and loLogic["get" .. p.capitalizeName(imgType) .. "ByName"](iconObject.name)) or nil |
||
+ | imageFile = p.assembleImage(imgType, icon, 32, {rarity = (loObj and loObj.rarity) or 1, iconLink = true}) |
||
+ | else |
||
+ | imageFile = link(filename, "64px") --iconObject.ilRes or "32px") |
||
+ | end |
||
+ | |||
+ | local tooltipText = link(filename, "96px", "link=") |
||
+ | |||
+ | --result = '<span class = "wrap-span pcView" style = "display:none;"><span class="box-span">' .. boxDesc .. '</span>' .. text .. file .. '</span>' |
||
+ | local padding = p.getPaddingsFromIcon(iconObject) |
||
+ | result = text .. |
||
+ | '<span class = "iconLink" style = "vertical-align: middle; padding: ' .. padding.top .. space .. padding.right .. space .. padding.bottom .. space .. padding.left .. '">' .. |
||
+ | '<span style = "display:none; width:' .. (iconObject.ilRes or "32px") .. semi .. 'max-height: ' .. (iconObject.ilRes or "32px") .. '" class = "pcView pcIconLink">' .. p.tooltip(imageFile, tooltipText, true, true, true) .. '</span>' .. |
||
+ | '<span class = "mobileView mobileIconLink">' .. link(filename, iconObject.ilRes or "32px") .. '</span>' .. |
||
+ | '</span>' |
||
+ | |||
+ | --mw.log(result) |
||
+ | return result |
||
+ | --return frame:expandTemplate{title = "IconLink", args = {icon, pageLink, displayText} } |
||
+ | end |
||
+ | |||
+ | function p.loadoutIconLink(icon, pageLink, displayText, params) |
||
+ | displayText = displayText or p.resolveParameter(icon, "displayText", true) or p.resolveParameter(icon, 3, true) |
||
+ | pageLink = pageLink or p.resolveParameter(icon, "link", true) or p.resolveParameter(icon, 2, true) |
||
+ | icon = p.resolveParameter(icon, "icon", true) or p.resolveParameter(icon, 1, true) or icon |
||
+ | local info = { |
||
+ | displayText = displayText or pageLink or icon, |
||
+ | link = pageLink or icon, |
||
+ | icon = icon |
||
+ | } |
||
+ | local iconObject, pluralType = p.getIconObject(info.icon) |
||
+ | local filename = cstr.file .. iconObject.iconFile .. tl .. "link=" .. (info.link or info.icon) |
||
+ | local tooltipText = link(filename, "96px", "link=") --default tooltip |
||
+ | |||
+ | if iconObject.iconFile:lower():find("perk") then |
||
+ | local loLogic = require("Module:Loadout" .. p.lang()) |
||
+ | tooltipText = loLogic.getPerkPageTable(params.perk) or "WIP/Error:" .. params.perk.name |
||
+ | end |
||
+ | |||
+ | local imageFile = link(filename, "64px") |
||
+ | local padding = p.getPaddingsFromIcon(iconObject) |
||
+ | local result = link(info.displayText, info.link) .. |
||
+ | '<span class = "iconLink" style = "vertical-align: middle; padding: ' .. padding.top .. space .. padding.right .. space .. padding.bottom .. space .. padding.left .. '">' .. |
||
+ | '<span style = "display:none; width:' .. (iconObject.ilRes or "32px") .. semi .. 'max-height: ' .. (iconObject.ilRes or "32px") .. '" class = "pcView pcIconLink">' .. p.tooltip(imageFile, tooltipText, true, true, true) .. '</span>' .. |
||
+ | --'<span class = "mobileView mobileIconLink">' .. link(filename, iconObject.ilRes or "32px") .. '</span>' .. |
||
+ | '</span>' |
||
+ | return result |
||
+ | end |
||
+ | |||
+ | function p.getPaddingsFromIcon(icon) |
||
+ | return { |
||
+ | top = icon.paddingTop or 0, |
||
+ | right = icon.paddingRight or 0, |
||
+ | bottom = icon.paddingBottom or 0, |
||
+ | left = icon.paddingLeft or 0 |
||
+ | } |
||
+ | end |
||
+ | |||
+ | function getBoxDescription(icon) --curently disabled |
||
+ | local ic = p.getIconObject(icon) |
||
+ | local result = cstr.empty |
||
+ | if ic.category == "Perks" then |
||
+ | result = '<h3>' .. icon .. '</h3><hr>'-- .. prkz.getPerkDescriptionByName(perk.name) |
||
+ | end |
||
+ | return result |
||
end |
end |
||
Line 291: | Line 703: | ||
local darkness = (0.299 * red + 0.587 * green + 0.114 * blue) / 255 |
local darkness = (0.299 * red + 0.587 * green + 0.114 * blue) / 255 |
||
− | mw.log("Red: " .. red .." |
+ | --mw.log("Red: " .. red .." \t\tor\t Blue: " .. blue .. " | Green: " .. green) |
− | mw.log(darkness) |
+ | --mw.log(darkness) |
if(darkness < _brigtnessTreshold) then |
if(darkness < _brigtnessTreshold) then |
||
return "white" |
return "white" |
||
Line 304: | Line 716: | ||
end |
end |
||
--------------------------------------------------------------------------------- |
--------------------------------------------------------------------------------- |
||
− | function p. |
+ | function p.getSumOfSizes(row) |
local result = 0 |
local result = 0 |
||
local i = 1 |
local i = 1 |
||
Line 314: | Line 726: | ||
end |
end |
||
+ | --[[ |
||
− | function compASTiles(row1, row2) |
||
+ | function compSize(row1, row2) |
||
− | local sum1 = row1.ASTiles |
||
− | local |
+ | local sum1 = row1.size |
+ | local sum2 = row2.size |
||
if(type(sum1) == "table") then |
if(type(sum1) == "table") then |
||
− | sum1 = p. |
+ | sum1 = p.getSumOfSizes(sum1) --converting back from table to number |
end |
end |
||
if(type(sum2) == "table") then |
if(type(sum2) == "table") then |
||
− | sum2 = p. |
+ | sum2 = p.getSumOfSizes(sum2) |
end |
end |
||
Line 334: | Line 747: | ||
end |
end |
||
end |
end |
||
+ | ]] |
||
− | |||
− | function compMapRateSize(row1, row2) |
||
− | return row1.size > row2.size --comparison specificly made for mapRates table |
||
− | end |
||
function compName(row1, row2) |
function compName(row1, row2) |
||
+ | if row1.diacritics then |
||
+ | if row2.diacritics then |
||
+ | return p.RemoveSpecialCharacters(row1.name, true, true) < p.RemoveSpecialCharacters(row2.name, true, true) |
||
+ | else |
||
+ | return p.RemoveSpecialCharacters(row1.name, true, true) < row2.name |
||
+ | end |
||
+ | elseif row2.diacritics then |
||
+ | return row1.name < p.RemoveSpecialCharacters(row2.name, true, true) |
||
+ | end |
||
return row1.name < row2.name |
return row1.name < row2.name |
||
end |
end |
||
− | function |
+ | function compFilename(row1, row2) |
+ | return row1.filename < row2.filename |
||
+ | end |
||
+ | |||
+ | function compInt(row1, row2) -- 1, 2, 3 |
||
return row1 < row2 |
return row1 < row2 |
||
end |
end |
||
− | function |
+ | function compIntReversed(row1, row2) -- 3, 2, 1 |
+ | return row1 > row2 |
||
− | local m = require("Module:Datatable") |
||
− | --mw.log(mw.dumpObject(maps)) |
||
− | table.sort(maps,compASTiles) |
||
− | --mw.log(mw.dumpObject(maps)) |
||
end |
end |
||
− | function |
+ | function compIntDesc(row1, row2) -- 3, 2, 1 |
+ | return row1 > row2 |
||
− | --mw.log(mw.dumpObject(rankTable)) |
||
+ | end |
||
− | table.sort(rankTable, compMapRateSize) |
||
+ | |||
− | --mw.log(mw.dumpObject(rankTable)) |
||
+ | function compLevel(row1, row2) |
||
+ | return row1.level < row2.level |
||
+ | end |
||
+ | |||
+ | function compDlcCategory(row1, row2) |
||
+ | if row1.category == row2.category then |
||
+ | return row1.id < row2.id |
||
+ | end |
||
+ | return row1.category < row2.category |
||
+ | end |
||
+ | |||
+ | function compId(row1, row2) |
||
+ | return row1.id < row2.id |
||
+ | end |
||
+ | |||
+ | function compRealCcyFirst(row1, row2) |
||
+ | ccy1 = isRealCcy(row1.ccy) |
||
+ | ccy2 = isRealCcy(row2.ccy) |
||
+ | if (ccy1 and ccy2) or (not ccy1 and not ccy2) then --TRUE and TRUE or FALSE and FALSE |
||
+ | return row1.ccy < row2.ccy |
||
+ | elseif ccy1 and not ccy2 then --TRUE and FALSE |
||
+ | return true |
||
+ | else |
||
+ | return false |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function compCharsKillersFirst(row1, row2) |
||
+ | char1 = row1.power ~= nil |
||
+ | char2 = row2.power ~= nil |
||
+ | if (char1 and char2) or (not char1 and not char2) then --TRUE and TRUE or FALSE and FALSE |
||
+ | return row1.id < row2.id |
||
+ | elseif char1 and not char2 then --TRUE and FALSE |
||
+ | return true |
||
+ | else |
||
+ | return false |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function rarityAndName(row1, row2) |
||
+ | if row1.rarity and row2.rarity and row1.rarity < row2.rarity then |
||
+ | return true |
||
+ | elseif row1.rarity and row2.rarity and row1.rarity > row2.rarity then |
||
+ | return false |
||
+ | elseif row1.pieces and row2.pieces then |
||
+ | local row1Name = cstr.empty |
||
+ | local row2Name = cstr.empty |
||
+ | for _, piece in pairs(row1.pieces) do row1Name = piece.name break end |
||
+ | for _, piece in pairs(row2.pieces) do row2Name = piece.name break end |
||
+ | return row1Name < row2Name |
||
+ | else |
||
+ | return compName(row1, row2) |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function p.sortByKeys(tableList) --only int, TODO check key type via pairs() fc |
||
+ | local result = {} |
||
+ | local orderedKeys = table.keys(tableList) |
||
+ | p.sortTable(orderedKeys, true) |
||
+ | |||
+ | for _, key in pairs(orderedKeys) do |
||
+ | result[key] = tableList[key] |
||
+ | end |
||
+ | return result |
||
+ | end |
||
+ | |||
+ | function p.sortByRDate(tableList, reversed) |
||
+ | table.sort(tableList, |
||
+ | function (row1, row2) |
||
+ | if p.toTimestamp(row1.rDate) < p.toTimestamp(row2.rDate) then |
||
+ | if reversed then |
||
+ | return false |
||
+ | else |
||
+ | return true |
||
+ | end |
||
+ | end |
||
+ | end |
||
+ | ) |
||
+ | return tableList |
||
+ | end |
||
+ | |||
+ | |||
+ | function p.sortMapsBySize() --deprecated? |
||
+ | local m = require("Module:Datatable" .. p.lang()) |
||
+ | --mw.log(mw.dumpObject(maps)) |
||
+ | table.sort(maps,compSize) |
||
+ | --mw.log(mw.dumpObject(maps)) |
||
end |
end |
||
Line 364: | Line 871: | ||
end |
end |
||
+ | function p.sortItemsBycompFilename(tableList) --used for charms (as perk object is not retrieved, only cosData record) |
||
− | function p.sortTable(tableList) |
||
− | table.sort(tableList, |
+ | table.sort(tableList, compFilename) |
end |
end |
||
+ | function p.sortTable(tableList, reversed) |
||
+ | if reversed then |
||
+ | table.sort(tableList, compIntReversed) |
||
+ | else |
||
+ | table.sort(tableList, compInt) |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function p.sortTableDesc(tableList) |
||
+ | table.sort(tableList, compIntDesc) |
||
+ | end |
||
+ | |||
+ | function p.sortDlcByCategory(tableList) |
||
+ | table.sort(tableList, compDlcCategory) |
||
+ | end |
||
+ | |||
+ | function p.sortPerksByLevel(tableList) |
||
+ | table.sort(tableList, compLevel) |
||
+ | end |
||
+ | |||
+ | function p.sortTableById(tableList) |
||
+ | table.sort(tableList, compId) |
||
+ | end |
||
+ | |||
+ | function p.sortRealCcyFirst(tableList) |
||
+ | table.sort(tableList, compRealCcyFirst) |
||
+ | end |
||
+ | |||
+ | function p.sortCharsKillersFirst(tableList) |
||
+ | table.sort(tableList, compCharsKillersFirst) |
||
+ | end |
||
+ | |||
+ | function p.sortCosmeticsByRarityAndName(tableList) |
||
+ | table.sort(tableList, rarityAndName) |
||
+ | end |
||
--------------------------------------------------------------------------------- |
--------------------------------------------------------------------------------- |
||
+ | -- Time functions -- |
||
+ | function p.today() |
||
+ | return os.time(os.date("!*t")) |
||
+ | end |
||
+ | function p.getYear(stamp) return p.getTimeAspect(stamp, "year") end |
||
− | function p.getCountOfSoundtracks() |
||
+ | function p.getMonth(stamp) return p.getTimeAspect(stamp, "month") end |
||
− | return utils.getCount("ost") |
||
+ | function p.getDay(stamp) return p.getTimeAspect(stamp, "day") end |
||
+ | function p.getHour(stamp) return p.getTimeAspect(stamp, "hour") end |
||
+ | function p.getMinute(stamp) return p.getTimeAspect(stamp, "minute") end |
||
+ | function p.getSecond(stamp) return p.getTimeAspect(stamp, "second") end |
||
+ | function p.getWeekday(stamp) return p.getTimeAspect(stamp, "weekday") end |
||
+ | |||
+ | function p.getTimeAspect(stamp, aspect) --aspect being selected time portion |
||
+ | if type(stamp) ~= 'number' then |
||
+ | stamp = p.toTimestamp(stamp) |
||
+ | end |
||
+ | local tag = '%c' |
||
+ | if aspect == "year" then tag = '%Y' |
||
+ | elseif aspect == "month" then tag = '%m' |
||
+ | elseif aspect == "day" then tag = '%d' |
||
+ | elseif aspect == "hour" then tag = '%H' |
||
+ | elseif aspect == "minute" then tag = '%M' |
||
+ | elseif aspect == "second" then tag = '%S' |
||
+ | elseif aspect == "weekday" then tag = '%w' |
||
+ | end |
||
+ | |||
+ | local result = os.date(tag, stamp) |
||
+ | return (aspect == "weekday" and tonumber(result)) or result |
||
end |
end |
||
− | function p. |
+ | function p.addTime(portion, amount, stamp) |
+ | stamp = stamp or 0 |
||
− | matchYear = "^(##)%.(##)%.(%d%d%d%d)$" |
||
+ | if type(stamp) ~= 'number' then |
||
− | matchMonth = "^(##)%.(%d%d)%.(%d%d%d%d)$" |
||
+ | stamp = p.toTimestamp(stamp) |
||
+ | end |
||
+ | |||
+ | local multiplier = timeContants[portion] |
||
+ | stamp = stamp + (amount * multiplier) |
||
+ | |||
+ | return stamp |
||
+ | end |
||
+ | |||
+ | function p.getTimeDiff(timestamp1, timestamp2) --First parameter is first in timeline, Second Parameter is later one |
||
+ | return os.difftime(timestamp2, timestamp1) |
||
+ | end |
||
+ | |||
+ | function p.getTimeDiffFormatting(seconds, unit) |
||
+ | return seconds / timeContants[unit] |
||
+ | end |
||
+ | |||
+ | function p.toTimestamp(datestamp) |
||
+ | if type(datestamp) == 'string' then |
||
+ | datestamp = p.GetDatetime(datestamp) |
||
+ | end |
||
+ | return os.time(datestamp) --if datestamp = nil, os.time() returns current datetime stamp |
||
+ | end |
||
+ | |||
+ | function p.toDate(timestamp, timeFormat) |
||
+ | return os.date(timeFormat or '%d %B %Y', timestamp) --21 October 2021 |
||
+ | end |
||
+ | |||
+ | function p.resolveDateTime(datetime, skipDay) --with weekday |
||
+ | local matchYear = "^(..)%.(..)%.(%d%d%d%d)$" |
||
+ | local matchMonth = "^(..)%.(%d%d)%.(....)$" |
||
+ | local matchDay = "^(%d%d)%.(..)%.(....)$" |
||
+ | local year, month, day, dayName = false |
||
local rDate = os.time(p.GetDatetime(datetime)) |
local rDate = os.time(p.GetDatetime(datetime)) |
||
if string.find(datetime, matchYear) then |
if string.find(datetime, matchYear) then |
||
− | + | year = os.date("%Y", rDate) |
|
− | elseif string.find(datetime, matchMonth) then |
||
− | return os.date("%B %Y", rDate) |
||
end |
end |
||
+ | if string.find(datetime, matchMonth) then |
||
− | return os.date("%d %B %Y (%A)", rDate) |
||
+ | month = os.date("%B", rDate) |
||
+ | end |
||
+ | if string.find(datetime, matchDay) then |
||
+ | day = os.date("%d", rDate) |
||
+ | dayName = os.date("%A", rDate) |
||
+ | end |
||
+ | |||
+ | --(day and month) is to avoid showing a day as the function sets the month to January (first month) by default, if the month is not provided |
||
+ | --day part: ((day and month and day .. space) or cstr.empty) |
||
+ | --month part: ((month and months[month] .. space) or cstr.empty) |
||
+ | --year part: year |
||
+ | --dayName part: ((day and month and not skipDay and space .. brackets(days[dayName])) or cstr.empty) |
||
+ | return ((day and month and day .. space) or cstr.empty) .. ((month and months[month] .. space) or cstr.empty) .. year .. ((day and month and not skipDay and space .. brackets(days[dayName])) or cstr.empty) |
||
end |
end |
||
+ | function p.isFullDateTime(datestamp) return p.IsFullDateTime(datestamp) end |
||
function p.IsFullDateTime(datestamp) |
function p.IsFullDateTime(datestamp) |
||
local day, month, year = datestamp:match("^(..)%.(..)%.(....)$") |
local day, month, year = datestamp:match("^(..)%.(..)%.(....)$") |
||
Line 392: | Line 1,005: | ||
end |
end |
||
+ | --convertToTimeVersion = normally, return table counts as 1.1.1970 (with day = 1, month = 1 and year = 1970). |
||
− | function p.GetDatetime(datestamp) |
||
+ | --But if we want to have time stamp (like remaining time) these default values add 1 day, 1 month and 1970 years to the countdown table (Time stamp version table) |
||
− | local day, month, year = datestamp:match("^(..)%.(..)%.(%d%d%d%d)$") |
||
+ | function p.GetDatetime(datestamp, convertToTimeVersion) |
||
− | if month == "##" then month = 1 end |
||
+ | local result = {} |
||
− | if day == "##" then day = 1 end |
||
+ | if type(datestamp) == types.string then |
||
− | return {year = year, month = month, day = day} |
||
+ | local day, month, year, hour, min, sec = datestamp:match("^(..)%.(..)%.(%d%d%d%d) ?(%d?%d?):?(%d?%d?):?(%d?%d?)$") |
||
+ | if month == "##" then |
||
+ | month = 1 |
||
+ | result.fakeMonth = true |
||
+ | end |
||
+ | if day == "##" then |
||
+ | day = 1 |
||
+ | result.fakeDay = true |
||
+ | end |
||
+ | |||
+ | result.year = tonumber(year) |
||
+ | result.month = tonumber(month) |
||
+ | result.day = tonumber(day) |
||
+ | result.hour = tonumber((hour ~= cstr.empty and hour) or 0) |
||
+ | result.min = tonumber((min ~= cstr.empty and min) or 0) |
||
+ | result.sec = tonumber((sec ~= cstr.empty and sec) or 0) |
||
+ | result.timestamp = p.toTimestamp(result) |
||
+ | |||
+ | local tempDateObj = os.date("!*t", os.time(result)) |
||
+ | result.yday = tempDateObj.yday --year day included |
||
+ | result.wday = p.correctWeekday(tempDateObj).wday --weekDay included |
||
+ | result.week = p.getWeekNumber(result.timestamp) |
||
+ | result.quarter = math.ceil(result.month / 3) |
||
+ | result.dst = tempDateObj.isdst |
||
+ | elseif type(datestamp) == types.number then |
||
+ | result = os.date("!*t", datestamp) |
||
+ | if(convertToTimeVersion) then |
||
+ | result.day = result.day - 1 --we have to subtract it, not set it, as the datetime stamp can reach to days and months |
||
+ | result.month = result.month - 1 |
||
+ | result.year = result.year - 1970 |
||
+ | end |
||
+ | result = p.correctWeekday(result) |
||
+ | result.timestamp = datestamp |
||
+ | end |
||
+ | |||
+ | return result |
||
end |
end |
||
+ | |||
+ | --.wday is indexed 1 - 7, Sunday being 1 => https://www.lua.org/manual/5.3/manual.html#:~:text=wday%20(weekday%2C%201%E2%80%937%2C%20Sunday%20is%C2%A01) |
||
+ | --%w starts 0 - 6, Sunday being 0 => https://www.lua.org/pil/22.1.html#:~:text=%25w,6%20%3D%20Sunday%2DSaturday%5D |
||
+ | --following row synchronises the convention with '%w' and shifting it to 1 - 7 starting Monday, as for the rest of Wiki |
||
+ | function p.correctWeekday(datetable) |
||
+ | datetable.wday = (datetable.wday == 1 and 7) or datetable.wday - 1 |
||
+ | return datetable |
||
+ | end |
||
+ | |||
+ | --ISO: https://en.wikipedia.org/wiki/ISO_week_date |
||
+ | --If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in W01. If it is on a Friday, it is part of W53 of the previous year. |
||
+ | --If it is on a Saturday, it is part of the last week of the previous year which is numbered W52 in a common year and W53 in a leap year. |
||
+ | --If it is on a Sunday, it is part of W52 of the previous year. |
||
+ | function p.getWeekNumber(dateStamp) --in milliseconds, os.time() |
||
+ | firstWeekDayOfYear = tonumber(os.date("%w", os.time({year = os.date("%Y", dateStamp), month = 1, day = 1}))) |
||
+ | firstWeekOfYear = tonumber(os.date("%W", os.time({year = os.date("%Y", dateStamp), month = 1, day = 1}))) |
||
+ | |||
+ | -- ATTENTION as per description above some years have first week 01 |
||
+ | -- because all first weeks of year that starts with Sunday, Saturday, Friday or Thursday must be shifted |
||
+ | if firstWeekDayOfYear > 0 and firstWeekDayOfYear < 4 and firstWeekOfYear == 0 then |
||
+ | return tonumber(os.date("%W", dateStamp)) + 1 |
||
+ | else |
||
+ | return tonumber(os.date("%W", dateStamp)) |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function p.getDatePart(datestamp, part) |
||
+ | local day, month, year = datestamp:match("^(..)%.(..)%.(....)$") |
||
+ | if year == "####" then month = false end |
||
+ | if month == "##" then month = false end |
||
+ | if day == "##" then day = false end |
||
+ | if part == "day" then return day |
||
+ | elseif part == "month" then return month |
||
+ | elseif part == "year" then return year |
||
+ | end |
||
+ | end |
||
+ | |||
+ | -------------------------------------------------------------------------------- |
||
function p.regularReplace(reggedString, regexTable) |
function p.regularReplace(reggedString, regexTable) |
||
− | local result = |
+ | local result = cstr.empty |
− | local regexString = regex --"#pl%((%d)%)" -- looking and extracting number from "#pl(x)" |
||
for key, value in pairs(regexTable) do |
for key, value in pairs(regexTable) do |
||
reggedString = reggedString:gsub(key, value) |
reggedString = reggedString:gsub(key, value) |
||
end |
end |
||
− | --for m in reggedString:gmatch(regex) do |
||
− | -- mw.log(mw.dumpObject(m)) |
||
− | -- result = reggedString:gsub(regexString, "string") |
||
− | -- mw.log(result) |
||
− | --end |
||
return reggedString |
return reggedString |
||
end |
end |
||
function p.getIcon(icon) |
function p.getIcon(icon) |
||
− | + | return p.getIconObject(icon).iconFile |
|
+ | end |
||
− | if type(icon) == "table" then |
||
+ | |||
− | icon = icon.args[1] or mw.title.getCurrentTitle().text |
||
+ | function p.getIconObject(icon) |
||
− | end |
||
+ | icon = p.resolveParameter(icon) |
||
− | require("Module:Datatable/Icons") |
||
+ | local icons = mw.loadData("Module:Datatable/Icons" .. p.lang()).icons --mw.loadData |
||
+ | local pluralRegex = "^(.+)s$" |
||
+ | local plural2Regex = "^(.+)es$" |
||
+ | local plural3Regex = "^(.+)ies$" |
||
+ | local plural = icon:match(pluralRegex) |
||
+ | local plural2 = icon:match(plural2Regex) --Toolboxes => Toolbox |
||
+ | local plural3 = icon:match(plural3Regex) --Bodies => Bod |
||
+ | local result = getIconElement(icons, icon, true) |
||
+ | if result then |
||
− | mw.log(icon) |
||
+ | return result |
||
− | for _, element in ipairs(icons) do |
||
+ | elseif plural and icons[plural] ~= nil then --Pallets => Pallet |
||
− | if icon == element.icon then |
||
+ | return getIconElement(icons, plural), 1 |
||
− | mw.log(element.iconFile) |
||
+ | elseif plural2 and icons[plural2] ~= nil then --Toolboxes => Toolbox |
||
− | return element.iconFile |
||
+ | return getIconElement(icons, plural2), 2 |
||
− | end |
||
+ | elseif plural3 and icons[plural3 .. "y"] ~= nil then --Bodies => Body |
||
+ | return getIconElement(icons, plural3 .. "y"), 3 |
||
+ | else |
||
+ | return getIconElement(icons, icon) |
||
end |
end |
||
− | return icons[1].iconFile --Icon not found, then pick the first one which is supposed to be the Unknown one |
||
end |
end |
||
− | function |
+ | function getIconElement(icons, icon, returnNil) |
+ | if type(icons[icon]) == types.table then |
||
− | local iconR = "" |
||
+ | local result = table.copy(icons[icon]) |
||
− | if type(icon) == "table" then |
||
+ | result.name = icon |
||
− | icon = icon.args[1] or mw.title.getCurrentTitle().text |
||
+ | return result |
||
+ | elseif returnNil then |
||
+ | return nil |
||
+ | elseif icons[icon] == nil then |
||
+ | return icons["Unknown QuestionMark"] or icons[1] |
||
+ | else |
||
+ | return getIconElement(icons, icons[icon]) |
||
end |
end |
||
− | require("Module:Datatable/Icons") |
||
− | |||
− | for _, element in ipairs(icons) do |
||
− | iconR = iconR .. "icon [" .. icon .. "] == [" .. element.icon .. "] element.icon<br>" |
||
− | if icon == element.icon then |
||
− | iconR = iconR .. "FOUND" |
||
− | return iconR |
||
− | end |
||
− | end |
||
− | iconR = iconR .. "NOT FOUND" |
||
− | return iconR |
||
end |
end |
||
Line 454: | Line 1,142: | ||
if character.id == id then return character end |
if character.id == id then return character end |
||
end |
end |
||
− | return "Character not found!" |
||
end |
end |
||
+ | function p.getCharacter(object) |
||
+ | local data = require("Module:Datatable" .. p.lang()) |
||
+ | |||
+ | if object.killer or object.charType == 'K' then |
||
+ | return p.getCharacterById(object.killer or object.character, data.killers) |
||
+ | else |
||
+ | return p.getCharacterById(object.survivor or object.character, data.survivors) |
||
+ | end |
||
+ | end |
||
+ | |||
+ | --function returns appropriate character name that should be compatible with IL. |
||
+ | --IL itself is not executed to have option to customise the IL call |
||
+ | function p.resolveCharacterIconLinkName(owner) |
||
+ | return (owner and owner.multiName and owner.realName) or (not p.isKiller(owner) and owner.shortName) or owner.name or false |
||
+ | end |
||
+ | |||
+ | function p.resolveCharacterPortraitFileName(character, maxId) |
||
+ | local fileConst = "_charPreview_portrait" |
||
+ | local isKiller = p.isKiller(character) |
||
+ | local unknownChar = (isKiller and "UnknownKiller") or "UnknownSurvivor" |
||
+ | local fileName |
||
+ | |||
+ | fileName = p.getFileNameFromTableById(character.id, (isKiller and killerImages) or survivorImages, "preview") --get custom name from table |
||
+ | if fileName == cstr.empty or not p.isValidFileName(fileName) then --K/S{ID}_charPreview_portrait |
||
+ | fileName = p.getCharacterIdentifier(character) .. fileConst |
||
+ | end |
||
+ | if (maxId and (character.id >= maxId - 1 and not p.isValidFileName(fileName)) or (not maxId and not p.isValidFileName(fileName))) then --File not Found; maxId - 1 to consider last two characters, not only the last one |
||
+ | fileName = unknownChar .. fileConst |
||
+ | end |
||
+ | |||
+ | --mw.log(fileName) |
||
+ | return fileName |
||
+ | end |
||
+ | |||
+ | function p.getCharacterIdentifier(character) |
||
+ | return ((p.isKiller(character) and 'K') or 'S') .. string.format("%02d", character.id) |
||
+ | end |
||
+ | |||
+ | function p.getFileNameFromTableById(id, fileTable, field) |
||
+ | --mw.log(id) |
||
+ | for j, sImage in ipairs(fileTable) do |
||
+ | if sImage.id == id and sImage[field] ~= nil then |
||
+ | return sImage[field] |
||
+ | end |
||
+ | end |
||
+ | return cstr.empty |
||
+ | end |
||
+ | |||
+ | --Not currently used |
||
function p.resolveGender(abbr) |
function p.resolveGender(abbr) |
||
if abbr == 'M' then return strings.male |
if abbr == 'M' then return strings.male |
||
elseif abbr == 'F' then return strings.female |
elseif abbr == 'F' then return strings.female |
||
− | + | elseif abbr == 'N' then return strings.nonhuman |
|
+ | elseif abbr == 'M/F' then return strings.male .. comma .. strings.female |
||
+ | elseif abbr == 'F/M' then return strings.female .. comma .. strings.male |
||
+ | elseif abbr == 'TM' then return strings.transmale |
||
+ | elseif abbr == 'TF' then return strings.transfemale |
||
+ | else return strings.undefined |
||
end |
end |
||
end |
end |
||
+ | --classes in form: "sortable, class2, class3 ,..." |
||
+ | function p.wrapBasicTable(content, classes, styles, skipNtl) |
||
+ | return '{| class = ' .. quotes('wikitable' .. ((classes and space .. p.getTableClasses(classes)) or cstr.empty)) .. ((styles and space .. 'style = ' .. quotes(styles)) or cstr.empty) .. nl .. |
||
+ | (not skipNtl and ntl .. nl or cstr.empty) .. content ..'|}' |
||
+ | end |
||
+ | |||
+ | function p.wrapTable(content, classes, styles, skipNtl) |
||
+ | return (classes and '{| class = ' .. quotes((p.getTableClasses(classes))) or cstr.empty) .. ((styles and space .. 'style = ' .. quotes(styles)) or cstr.empty) .. nl .. |
||
+ | (not skipNtl and ntl .. nl or cstr.empty) .. content ..'|}' |
||
+ | end |
||
+ | |||
+ | function p.getTableClasses(classes) |
||
+ | classes = p.resolveParameter(classes, 1) |
||
+ | local unknownClass = "unknownClass" |
||
+ | if not classes then return unknownClass end |
||
+ | |||
+ | if type(classes) == "string" then |
||
+ | classes = classes:gsub(", ", space):gsub(",", space) |
||
+ | else --todo type(classes) == "table" |
||
+ | return unknownClass |
||
+ | end |
||
+ | return classes |
||
+ | end |
||
+ | |||
+ | function p.getCharacterByName(name) |
||
+ | name = name or p.resolveParameter(name) |
||
+ | local str = require("Module:Datatable" .. p.lang()) |
||
+ | for _, surv in ipairs(survivors) do |
||
+ | if surv.shortName == name or surv.name == name then |
||
+ | local survivor = table.copy(surv) |
||
+ | survivor.isKiller = false |
||
+ | return survivor |
||
+ | end |
||
+ | end |
||
+ | for _, killer in ipairs(killers) do |
||
+ | if killer.shortName == name or killer.realName == name or the(killer) .. killer.name == name or killer.name == name then |
||
+ | local klr = table.copy(killer) |
||
+ | klr.isKiller = true |
||
+ | return klr |
||
+ | end |
||
+ | end |
||
+ | return nil |
||
+ | end |
||
+ | |||
+ | --deprecated |
||
+ | function p.getCharsByDlc(dlc, charType) |
||
+ | local data = require("Module:Datatable" .. p.lang()) |
||
+ | local result = {} |
||
+ | local listTable = (charType == 'S' and data.survivors) or (charType == nil and data.survivors) or data.killers --if the charType is not set then set the table by default to survivors |
||
+ | |||
+ | for _, character in ipairs(listTable) do |
||
+ | if character.dlc == dlc.id then |
||
+ | result[#result + 1] = character |
||
+ | end |
||
+ | end |
||
+ | |||
+ | if charType ~= nil then --if the charType is set, we need loop through only one table. Otherwise charType wasn't set in order to retrieve both types of characters from DLC |
||
+ | return result |
||
+ | end |
||
+ | |||
+ | for _, character in ipairs(data.killers) do --since the default table is survivor, we can hardcode killer table as a second table |
||
+ | if character.dlc == dlc.id then |
||
+ | result[#result + 1] = character |
||
+ | end |
||
+ | end |
||
+ | return result |
||
+ | end |
||
+ | |||
+ | function p.getCharacterFirstName(character) |
||
+ | character = p.resolveParameter(character) |
||
+ | if not character.name then |
||
+ | character = p.getCharacterByName(character) |
||
+ | end |
||
+ | local regex = '([^ ]*) ?' --%a doesn't consider diacritics such as É (for instance: Élodie) |
||
+ | local isKiller = p.isKiller(character) |
||
+ | local text = (isKiller and character.name) or character.shortName or character.name --if the char is killer then use their name, otherwise it's survivor, thus use shortName or name |
||
+ | if isKiller then return text end --if it's killer, then their nickname is already what it's supposed to return |
||
+ | |||
+ | if regex and text:gmatch(regex) then |
||
+ | for m in text:gmatch(regex) do |
||
+ | return m --return first occurence before potential space, otherwise return whole string |
||
+ | end |
||
+ | end |
||
+ | return "Name not found" |
||
+ | end |
||
+ | |||
+ | function p.replaceLastSpaceByNBSP(text) -- Non Breakable SPace |
||
+ | local lastCharSpaceRegex = "^(.+) $" |
||
+ | local regex = "^(.+) (.+)$" --should pick last space(?) |
||
+ | text = text:gsub(lastCharSpaceRegex, "%1" .. nbspC):gsub(regex, "%1" .. nbspC .. "%2") |
||
+ | return text |
||
+ | end |
||
+ | |||
+ | function p.isCharacterKiller(character) return p.isKiller(character) end |
||
+ | function p.isKiller(character) |
||
+ | local character = p.resolveParameter(character) |
||
+ | return character.radius ~= nil |
||
+ | end |
||
+ | |||
+ | function p.getPossessiveName(character, params, fullName) |
||
+ | local langs = require("Module:Languages") |
||
+ | params = params or {link = p.bool(p.resolveParameter(character, "link", true)) or false} |
||
+ | params.fullName = fullName or p.bool(p.resolveParameter(character, "fullName", true)) or false |
||
+ | |||
+ | if not character.name then |
||
+ | character = p.getCharacterByName(p.resolveParameter(character)) |
||
+ | end |
||
+ | if not character then return false end |
||
+ | |||
+ | params.firstName = p.getCharacterFirstName(character) |
||
+ | |||
+ | return langs.evaluatePossessive(character, params) |
||
+ | end |
||
+ | |||
+ | --[[ |
||
+ | function p.getDativeName(character, params) |
||
+ | params = params or {link = p.bool(p.resolveParameter(character, "link", true)) or false} |
||
+ | if not character.name then |
||
+ | character = p.getCharacterByName(p.resolveParameter(character)) |
||
+ | end |
||
+ | if _language ~= "en" then |
||
+ | return langs["dative_" .. _language](character, params) .. firstName |
||
+ | end |
||
+ | |||
+ | return firstName |
||
+ | end |
||
+ | ]] |
||
+ | |||
+ | function p.clr(color, text) |
||
+ | text = text or p.resolveParameter(color, 2, true) |
||
+ | color = p.resolveParameter(color) |
||
+ | |||
+ | return clr(color, text) |
||
+ | --return frame:expandTemplate{title = "clr", args = {color, text}} |
||
+ | end |
||
+ | |||
+ | |||
+ | function p.biclr(color, text) return i(p.clr(color, text)) end |
||
+ | function p.ibclr(color, text) return i(p.clr(color, text)) end |
||
+ | |||
+ | --Colour Ordered |
||
+ | function p.clro(text, reset) |
||
+ | reset = reset or p.bool(p.resolveParameter(text, 2, true)) or false |
||
+ | text = p.resolveParameter(text, 1) |
||
+ | |||
+ | if not p.bool(frame:callParserFunction("#varexists:colorOrderCounter")) or reset then |
||
+ | frame:callParserFunction("#vardefine:colorOrderCounter", 1 ) |
||
+ | end |
||
+ | local order = tonumber(frame:callParserFunction("#var:colorOrderCounter")) % (#_colorOrder + 1) --making list infinite by going back |
||
+ | order = (order == 0 and 1) or order --adjusting an index from order = #colorOrder => 0 + 1 = 1 |
||
+ | frame:callParserFunction("#vardefine:colorOrderCounter", order + 1 ) |
||
+ | |||
+ | return ((text ~= nil and text ~= cstr.empty) and b(clr(_colorOrder[order], text))) or clr(_colorOrder[order], text) |
||
+ | end |
||
+ | |||
+ | --Colour Ordered Reset |
||
+ | function p.clror(text) |
||
+ | return p.clro(text, true) |
||
+ | end |
||
+ | |||
+ | function p.clroTest() |
||
+ | local words = string.split(strings.clrTestString, space) |
||
+ | local result = cstr.empty |
||
+ | local clrOffset = 0 |
||
+ | for i, word in ipairs(words) do |
||
+ | if #word <= 2 then clrOffset = clrOffset - 1 end |
||
+ | local clrIndex = i + clrOffset |
||
+ | local color = _colorOrder[clrIndex] |
||
+ | result = result .. (#word > 2 and p.tooltip(((i == 1 and p.clror(word)) or p.clro(word)), "order: " .. clrIndex .. br .. "colour: " .. color, true, true) or word) .. (clrIndex < #_colorOrder and space or cstr.empty) |
||
+ | if clrIndex == #_colorOrder then break end |
||
+ | end |
||
+ | return result .. ((#words > #_colorOrder and "...") or (#words < #_colorOrder and dot .. space .. brackets(strings.notEnoughColors)) or cstr.empty) |
||
+ | end |
||
+ | |||
+ | function p.bclr(color, text) |
||
+ | return b(p.clr(color, text)) |
||
+ | end |
||
+ | |||
+ | function clr(color, text) |
||
+ | local result = 'inherit' --CSS value, don't change it |
||
+ | --mw.log(tonumber(color, 10)) |
||
+ | color = tonumber(color, 10) or color:lower() --if the string is int then convert it to integer |
||
+ | |||
+ | if color == 1 or color == "brown" then result = "ab713c" |
||
+ | elseif color == "1bg" then result = "5d4533" |
||
+ | elseif color == 2 or color == "yellow" then result = "e8c252" |
||
+ | elseif color == "2bg" then result = "d7ad2f" |
||
+ | elseif color == 3 or color == "green" then result = "199b1e" |
||
+ | elseif color == "3bg" then result = "0f791f" |
||
+ | elseif color == 4 or color == "purple" then result = "ac3ee3" |
||
+ | elseif color == "4bg" then result = "672d7f" |
||
+ | elseif color == 5 or color == "pink" then result = "ff0955" |
||
+ | elseif color == "5bg" then result = "cf0b45" |
||
+ | elseif color == 6 or color == "orange" then result = "ff8800" |
||
+ | elseif color == "6bg" then result = "ff5300" |
||
+ | elseif color == 7 or color == "grey" then result = "808080" |
||
+ | elseif color == 8 or color == "red" then result = "d41c1c" |
||
+ | elseif color == 9 or color == "beige" then result = "e7cda2" |
||
+ | elseif color == 10 or color == "blue" then result = "0e98ff" |
||
+ | elseif color == 11 or color == "violet" then result = "b91a9b" |
||
+ | elseif color == "11bg" then result = "800080" |
||
+ | elseif color == 12 or color == "light blue" then result = "9bb0bf" |
||
+ | elseif color == "12bg" then result = "9bb0bf" |
||
+ | elseif color == 13 or color == "faded jade" then result = "418284" |
||
+ | elseif color == 14 or color == "gold" then result = "ffa800" |
||
+ | elseif color == "14bg" then result = "ffa800" |
||
+ | elseif color == 15 or color == "fuchsia" then result = "ec0dea" |
||
+ | elseif color == "15bg" then result = "b30ad2" |
||
+ | elseif color == 16 or color == "white" then result = "ffffff" |
||
+ | elseif color == 17 or color == "vomit green" then result = "8ad672" |
||
+ | elseif color == 18 or color == "blood red" then result = "900a0a" |
||
+ | elseif color == 19 or color == "bright red" then result = "ff0000" |
||
+ | elseif color == 20 or color == "silver" then result = "b5afb0" |
||
+ | elseif color == 21 or color == "turquoise" then result = "37d1c0" |
||
+ | elseif color == 22 or color == "cinnamon" then result = "b74004" |
||
+ | elseif color == 23 or color == "black" then result = "000000" |
||
+ | elseif color == 24 or color == "wiki gold" then result = "b7a269" |
||
+ | elseif color == 25 or color == "bronze" then result = "c2593a" |
||
+ | elseif color == 26 or color == "chartreuse" then result = "b6fa36" |
||
+ | elseif color == 27 or color == "screamin' green" then result = "63ef98" |
||
+ | elseif color == 28 or color == "lavender" then result = "b57edc" |
||
+ | elseif color == 29 or color == "aquamarine" then result = "7fffd4" |
||
+ | elseif color == 30 or color == "ultramarine" then result = "120a8f" |
||
+ | elseif color == 31 or color == "olive" then result = "808000" |
||
+ | elseif color == 32 or color == "pale rose" then result = "f5bfd9" |
||
+ | end |
||
+ | |||
+ | if text ~= nil and text ~= cstr.empty then |
||
+ | result = '<span class="luaClr clr clr' .. color .. '" style="color: ' .. ((result ~= 'inherit' and '#') or cstr.empty) .. result .. ';">' .. text .. '</span>' |
||
+ | end |
||
+ | |||
+ | return result |
||
+ | end |
||
+ | |||
+ | --calls for Strings function Syntax Highlight |
||
+ | function p.sh(value, lang, inline) |
||
+ | inline = p.bool(inline or p.resolveParameter(value, 3, true)) |
||
+ | lang = lang or p.resolveParameter(value, 2, true) |
||
+ | value = p.resolveParameter(value) |
||
+ | return sh(value, lang, inline) |
||
+ | end |
||
+ | |||
+ | --text = text containing a tooltip |
||
+ | --tooltip = tooltip content |
||
+ | --iconless = true/false whether the (i) icon should appear after the text containing tooltip (default false) |
||
+ | --borderless = true/false whether underline should be shown (default false) |
||
+ | function p.tooltip(text, tooltip, iconless, borderless, pcViewOnly) |
||
+ | borderless = borderless or p.resolveParameter(text, 4, true) or p.resolveParameter(text, "borderless", true) |
||
+ | iconless = p.bool(iconless or p.resolveParameter(text, 3, true) or (p.resolveParameter(text, "tooltip") and p.resolveParameter(text, 2, true)) or p.resolveParameter(text, "iconless")) |
||
+ | tooltip = tooltip or p.resolveParameter(text, 2, true) or p.resolveParameter(text, "tooltip") |
||
+ | text = p.resolveParameter(text) |
||
+ | local containsLink = p.containsLink(tooltip) |
||
+ | |||
+ | return |
||
+ | '<span class = "tooltip' .. ((containsLink and space .. 'linkIncluded') or cstr.empty) .. ((borderless and space .. 'borderless') or cstr.empty) .. '">' .. |
||
+ | '<span class="tooltipBaseText' .. ((iconless and space .. 'iconless') or cstr.empty) .. '">' .. text .. '</span>'.. |
||
+ | (((tooltip ~= nil and tooltip ~= cstr.empty) and |
||
+ | '<span class = "tooltiptext">' .. |
||
+ | ((not pcViewOnly and '<span class = "mobileView"> (</span>') or cstr.empty) .. --if it's mobile view then apply add brackets... |
||
+ | '<span class="tooltipTextWrapper">' .. tooltip .. '</span>' .. |
||
+ | ((not pcViewOnly and '<span class = "mobileView">) </span>') or cstr.empty) .. |
||
+ | '</span>') or cstr.empty) .. |
||
+ | '</span>' |
||
+ | end |
||
+ | |||
+ | function p.containsLink(text) |
||
+ | local regex = '%[%[.-%]%]' |
||
+ | local regexExternal = '%[.-%]' |
||
+ | local rawRegex = '?%&%#91;%&%#91;.-%&%#93;%&%#93;' |
||
+ | local rawRegexExternal = '%&%#91;.-%&%#93;' |
||
+ | if string.match(text, regex) or string.match(text, rawRegex) or |
||
+ | string.match(text, regexExternal) or string.match(text, rawRegexExternal) |
||
+ | then --we want to return boolean, not the index |
||
+ | return true |
||
+ | else |
||
+ | return false |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function p.ptb(content, title, patch) |
||
+ | return |
||
+ | ptb( |
||
+ | p.resolveParameter(content), |
||
+ | title or p.resolveParameter(content, 2, true), |
||
+ | patch or p.resolveParameter(content, 3, true) |
||
+ | ) |
||
+ | end |
||
+ | |||
+ | function ptb(content, title, patch) |
||
+ | title = title or p.resolveParameter(content, 2, true) |
||
+ | patch = patch or p.resolveParameter(content, 3, true) |
||
+ | |||
+ | if not patch or p.isPatchLive(patch) then |
||
+ | return content |
||
+ | else |
||
+ | return |
||
+ | dynamicBox(content, title, "ptb") |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function p.deletedBox(content, title, patch) |
||
+ | return |
||
+ | deletedBox( |
||
+ | p.resolveParameter(content), |
||
+ | title or p.resolveParameter(content, 2, true), |
||
+ | patch or p.resolveParameter(content, 3, true) |
||
+ | ) |
||
+ | end |
||
+ | |||
+ | function deletedBox(content, title, patch) |
||
+ | local result = dynamicBox(content, title, "deletedBox") |
||
+ | patch = (patch and string.trim(patch)) or nil |
||
+ | |||
+ | if patch and not p.isPatchLive(patch) then |
||
+ | --result = includeonly(result) .. noinclude(category("DeleteBox Remove Lists", "Remove List " .. patch)) |
||
+ | result = result .. category("DeleteBox Remove Lists", "DeleteBox List for " .. patch) |
||
+ | end |
||
+ | |||
+ | return result --no further logic so redirecting straight to dynamic box |
||
+ | end |
||
+ | |||
+ | function p.wipBox(content, title, patch) |
||
+ | return |
||
+ | wipBox( |
||
+ | p.resolveParameter(content), |
||
+ | title or p.resolveParameter(content, 2, true), |
||
+ | patch or p.resolveParameter(content, 3, true) |
||
+ | ) |
||
+ | end |
||
+ | |||
+ | function wipBox(content, title, patch) |
||
+ | local result = dynamicBox(content, title, "wipBox") |
||
+ | return result --no further logic so redirecting straight to dynamic box |
||
+ | end |
||
+ | |||
+ | function dynamicBox(content, title, boxType) |
||
+ | title = title or p.resolveParameter(content, 2, true) |
||
+ | boxType = boxType or p.resolveParameter(content, 3, true) |
||
+ | |||
+ | if not boxType then |
||
+ | return strings.boxTypeNotFound |
||
+ | end |
||
+ | |||
+ | return |
||
+ | '<div class = "dynamicBox ' .. boxType .. '">' .. |
||
+ | ((title and '<div class = "dynamicTitle ' .. boxType .. '">' .. title .. '</div>' ) or cstr.empty) .. |
||
+ | '<div class = "dynamicContent ' .. boxType .. '">' .. content .. '</div>' .. |
||
+ | '</div>' |
||
+ | end |
||
+ | |||
+ | function p.isPatchLive(patchParam) |
||
+ | local patch = p.getPatch(patchParam) |
||
+ | |||
+ | --dLog(((patchParam and "[" .. patchParam .. ((not patch and " - Not Found") or cstr.empty) .. "]") or cstr.empty) .. " isPatchLive: " .. (patch and (p.IsFullDateTime(patch.rDate) and tostring(p.dateHasPassed(p.GetDatetime(patch.rDate)))) or tostring(false))) |
||
+ | return patch and (p.IsFullDateTime(patch.rDate) and p.dateHasPassed(p.GetDatetime(patch.rDate))) or false |
||
+ | end |
||
+ | |||
+ | function p.isCharacterLive(character) |
||
+ | if character.dlc then |
||
+ | local dlcM = require("Module:DLCs") |
||
+ | local dlc = dlcM.getCharacterMainDlc(character) |
||
+ | return (dlc == nil) or p.IsFullDateTime(dlc.rDate) and p.dateHasPassed(dlc.rDate) |
||
+ | end |
||
+ | return false |
||
+ | end |
||
+ | |||
+ | function p.getPatch(patch) |
||
+ | local data = require("Module:Datatable" .. p.lang()) |
||
+ | |||
+ | for _, patchObj in ipairs(data.patches) do |
||
+ | if patchObj.patch == patch then return patchObj end |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function p.getLatestPatch() |
||
+ | return require("Module:Datatable" .. p.lang()).latestPatch.patch |
||
+ | end |
||
+ | |||
+ | --timestamp = {year = 2022, month = 7, day = 19} --example format |
||
+ | function p.dateHasPassed(timestamp) |
||
+ | local currentDate = os.date("*t") --gives current timestamp: {year = 1998, month = 9, day = 16, yday = 259, wday = 4, hour = 23, min = 48, sec = 10, isdst = false} |
||
+ | local releaseOffset = 60 * 60 * 15 --release hour 15:00 (server time is -2 hours from my current time) |
||
+ | timestamp = p.standardiseDateObject(timestamp) |
||
+ | |||
+ | --mw.log(os.difftime(os.time(timestamp) + releaseOffset, os.time(currentDate))) |
||
+ | return os.difftime(os.time(timestamp) + releaseOffset, os.time(currentDate)) < 0 -- subtracting releaseOffset to adjust timing according to actual release hour |
||
+ | end |
||
+ | |||
+ | function p.isEventLive(element) |
||
+ | --not element.event is to skip events from different tables, such as tomes |
||
+ | return not (element.event or element.patch) and (element.rDate and p.dateHasPassed(element.rDate)) and (element.eDate == nil or not p.dateHasPassed(element.eDate)) |
||
+ | or (element.patch and element.rDate and p.isFullDateTime(element.rDate) and p.dateHasPassed(element.rDate) and (element.eDate == nil or not p.dateHasPassed(element.eDate))) |
||
+ | end |
||
+ | |||
+ | function p.standardiseDateObject(dateObj) |
||
+ | if type(dateObj) == types.table then --if we pass table, we need to check time values and populate them to 0 by default |
||
+ | if not dateObj.hour then dateObj.hour = 0 end |
||
+ | if not dateObj.min then dateObj.min = 0 end |
||
+ | if not dateObj.sec then dateObj.sec = 0 end |
||
+ | |||
+ | dateObj = os.time(dateObj) --we convert it to seconds constant (resp. variable) |
||
+ | end |
||
+ | |||
+ | dateObj = p.GetDatetime(dateObj) |
||
+ | return dateObj |
||
+ | end |
||
+ | |||
+ | function p.resolvePatchList() |
||
+ | local data = require("Module:Datatable" .. p.lang()) |
||
+ | local result = cstr.empty |
||
+ | local yearCounter = 0 |
||
+ | local tempResult = cstr.empty |
||
+ | |||
+ | for _, patch in ipairs(data.patches) do |
||
+ | local patchDate = p.standardiseDateObject(patch.rDate) |
||
+ | local suffixLink = getSuffixLink(patch) |
||
+ | local suffixPatchName = getSuffixPatchName(patch) --in case of PTB we need to attach " (PTB)" suffix |
||
+ | |||
+ | if yearCounter ~= patchDate.year then |
||
+ | if yearCounter ~= 0 then |
||
+ | result = result .. frame:callParserFunction{ name = '#tag', args = {"tabber", tempResult} } .. nl |
||
+ | end |
||
+ | tempResult = cstr.empty |
||
+ | yearCounter = patchDate.year |
||
+ | result = result .. |
||
+ | "|-|" .. patchDate.year .. "=" .. nl |
||
+ | end |
||
+ | |||
+ | local patchNotes, result = pcall(function () return frame:expandTemplate{title = strings.patch .. space .. patch.patch .. suffixPatchName} end) |
||
+ | if patchNotes and --[[not patch.ptb and]] patch ~= patches[#patches] then |
||
+ | local patchVersions = string.split(patch.patch, '.') |
||
+ | if patchVersions[3] ~= "0" then patchVersions[3] = "0" end |
||
+ | for i = #patchVersions, 4 , -1 do --in case that patch contains anything else after last patch number let's remove it (example: "6.1.2 / 6.1.3") |
||
+ | table.remove(patchVersions) |
||
+ | end |
||
+ | local patchLink = strings.patch .. space .. patch.patch .. suffixPatchName |
||
+ | local patchLinkModified = strings.patch .. space .. table.concat(patchVersions, ".") |
||
+ | |||
+ | tempResult = tempResult .. |
||
+ | frame:expandTemplate{title = "!"}.. "-" .. frame:expandTemplate{title = "!"} .. patch.patch .. suffixPatchName .. "=" .. nl .. --creating individual patch tabs |
||
+ | |||
+ | frame:expandTemplate{title = "(!"} .. space .. 'class = "wikitable"' .. nl .. |
||
+ | "!" .. nl .. "==== " .. link(patchLinkModified .. ((suffixLink and suffixLink .. tl .. patchLink) or cstr.empty)) .. " - " .. p.toDate(os.time(patchDate)) .. " ====" .. nl .. --28 April 2022 |
||
+ | frame:expandTemplate{title = "!-"} .. nl .. |
||
+ | frame:expandTemplate{title = "!"} .. result .. nl .. |
||
+ | frame:expandTemplate{title = "!)"} .. nl |
||
+ | end |
||
+ | |||
+ | end |
||
+ | result = result .. frame:callParserFunction{ name = "#tag", args = {"tabber", tempResult} } .. nl --post loop aiditon for last year |
||
+ | |||
+ | result = frame:extensionTag{name = 'tabber',content = result} |
||
+ | |||
+ | return result |
||
+ | end |
||
+ | |||
+ | function getSuffixPatchName(patch) |
||
+ | if patch.ptb then |
||
+ | return " (PTB)" |
||
+ | end |
||
+ | return cstr.empty |
||
+ | end |
||
+ | |||
+ | function getSuffixLink(patch) |
||
+ | local versions = string.split(patch.patch, '.') |
||
+ | if versions[3] ~= "0" then |
||
+ | return "#Hotfix " .. patch.patch |
||
+ | elseif patch.ptb then |
||
+ | return "#Public Test Build " .. patch.patch --patch needs to not contain "(PTB)" part |
||
+ | end |
||
+ | return nil |
||
+ | end |
||
+ | |||
+ | function p.getPatchLink(patch) |
||
+ | local suffixLink = getSuffixLink(patch) |
||
+ | local suffixPatchName = getSuffixPatchName(patch) --in case of PTB we need to attach " (PTB)" suffix |
||
+ | local patchLink = strings.patch .. space .. patch.patch .. suffixPatchName |
||
+ | local patchVersions = string.split(patch.patch, '.') |
||
+ | if patchVersions[3] ~= "0" then patchVersions[3] = "0" end |
||
+ | local patchLinkModified = strings.patch .. space .. table.concat(patchVersions, ".") |
||
+ | |||
+ | return link(patchLinkModified .. ((suffixLink and suffixLink .. tl .. patchLink) or cstr.empty)) |
||
+ | end |
||
+ | |||
+ | --wt = wordTable |
||
+ | function p.getWord(wt, index) |
||
+ | local keys = table.keys(wt) |
||
+ | local maxValue = table.max(keys) |
||
+ | local minValue = table.min(keys) |
||
+ | if index > maxValue then return wt[maxValue] end --if the index is higher than highest amount, return word of highest amount available |
||
+ | if wt[index] then return wt[index] end -- if the index is exactly of plural count then return exact word on index |
||
+ | if index < minValue then return wt[minValue] end --if index not found, it means it's lower than lowest amount in a word table |
||
+ | |||
+ | for i, keyValue in ipairs(keys) do |
||
+ | if index < keyValue then |
||
+ | return wt[keys[i - 1]] --if the index is between two amounts(indexes) use the lower one |
||
+ | end |
||
+ | end |
||
+ | end |
||
+ | |||
+ | --{{header = 2022, content = "..."}, {}, ...} |
||
+ | function p.getTabberFromTable(tabberTable) |
||
+ | local result = cstr.empty |
||
+ | for _, section in ipairs(tabberTable) do |
||
+ | result = result .. nl .. '|-|' .. (section.header or strings.tabberHeaderNotFound) .. '=' .. nl .. section.content .. nl |
||
+ | end |
||
+ | |||
+ | result = frame:callParserFunction{ name = '#tag', args = {"tabber", result} } |
||
+ | |||
+ | return result |
||
+ | end |
||
+ | |||
+ | function p.isTagPresent(el, searchedTag) |
||
+ | if el.tags and type(el.tags) == types.table then |
||
+ | for _, tag in ipairs(el.tags) do |
||
+ | if string.lower(searchedTag) == string.lower(tag) then |
||
+ | return true |
||
+ | end |
||
+ | end |
||
+ | end |
||
+ | return false |
||
+ | end |
||
+ | |||
+ | function p.yesNo(param, note, imgSize) |
||
+ | imgSize = imgSize or p.resolveParameter(param, 3, true) or p.resolveParameter(param, "size", true) |
||
+ | note = note or p.resolveParameter(param, 2, true) or p.resolveParameter(param, "note", true) |
||
+ | param = p.resolveParameter(param, 1, true) or param |
||
+ | local imageFilename = "Unknown QuestionMark.png" |
||
+ | local tickClass = "unknown" |
||
+ | local size = (imgSize or 32) .. "px" |
||
+ | if p.bool(param) == true then |
||
+ | imageFilename = "DBD_UI_Icon_CheckMark.png" |
||
+ | tickClass = "tickYes" |
||
+ | elseif p.bool(param) == false then |
||
+ | imageFilename = "DBD_UI_Icon_X.png" |
||
+ | tickClass = "tickNo" |
||
+ | elseif param:lower() == "nan" then |
||
+ | imageFilename = "NaN.png" |
||
+ | tickClass = "tickNaN" |
||
+ | end |
||
+ | |||
+ | --lg(span(p.tooltip(span(file(imageFilename, size), tickClass),cstr.empty,true,true), "yesNo")) |
||
+ | return |
||
+ | span( |
||
+ | p.tooltip( |
||
+ | span(file(imageFilename, size), tickClass), |
||
+ | note or cstr.empty, |
||
+ | true, |
||
+ | true |
||
+ | ) |
||
+ | , "yesNo") |
||
+ | end |
||
+ | |||
+ | --imgType: |
||
+ | -- perk = perk with switching background |
||
+ | -- sosPerk = teachable frame background |
||
+ | --icon icon that is inside |
||
+ | --imgSize currently not used, should be available for manually adjusting the size of image |
||
+ | function p.assembleImage(imgType, icon, imgSize, params) |
||
+ | local variousLogic = require("Module:Various") |
||
+ | |||
+ | params = params or p.resolveParameter(imgType, 4, true) --not sure if table can be passed directly from wiki |
||
+ | local rarity = p.resolveParameter(imgType, "rarity", true) |
||
+ | local isIconLink = p.resolveParameter(imgType, "iconLink", true) or p.resolveParameter(imgType, "il", true) or (params and params.iconLink == true) or false |
||
+ | local rarityNumber = tonumber(rarity) or (params and params.rarity and tonumber(params.rarity)) |
||
+ | rarity = string.replace(((rarityNumber and (variousLogic.rarity[rarityNumber].techName or variousLogic.rarity[rarityNumber].name)) or (rarity or (params and params.rarity)) or variousLogic.rarity[1].techName or variousLogic.rarity[1].name):lower(), space, dash) --Common by default |
||
+ | rarity = string.replace(rarity, "&", "and") |
||
+ | |||
+ | imgSize = imgSize or p.resolveParameter(imgType, 3, true) or p.resolveParameter(imgType, "size", true) |
||
+ | if type(imgSize) == types.string then return "Error: " .. imgSize end |
||
+ | icon = icon or p.resolveParameter(imgType, 2, true) or p.resolveParameter(imgType, "icon", true) |
||
+ | imgType = p.resolveParameter(imgType, "type", true) or p.resolveParameter(imgType) |
||
+ | |||
+ | local imgSizeStyle = (imgSize and ' style = "width: ' .. imgSize .. 'px; height: ' .. imgSize .. 'px;"') or cstr.empty |
||
+ | local align = (params and params.align and "float: " .. params.align .. semi) or cstr.empty |
||
+ | |||
+ | if imgType == "sosPerk" then |
||
+ | return |
||
+ | '<div class = "game-element-container inline-flex relative">' .. |
||
+ | '<div class = "game-element-bg-settings game-element-bg-settings-size-sos teachable-perk-element"' .. imgSizeStyle .. '></div>' .. |
||
+ | '<div class = "game-element-bg-settings-size-sos game-element-imgObject absolute"' .. imgSizeStyle .. '>' .. file(p.getIcon(icon), "link=" .. icon) .. '</div>' .. |
||
+ | '<div class = "game-element-bg-settings game-element-bg-settings-size-sos teachable-indicator-perk-element absolute"' .. imgSizeStyle .. '></div>' .. |
||
+ | '</div>' |
||
+ | elseif imgType == "perk" then |
||
+ | return |
||
+ | '<div class = "game-element-container flex relative">' .. |
||
+ | '<div class = "game-element-bg-settings game-element-bg-settings-size-perk animate-perk-settings animate-perk-bg"' .. imgSizeStyle .. '></div> ' .. |
||
+ | '<div class = "game-element-bg-settings game-element-bg-settings-size-perk animate-perk-settings animate-perk-level absolute"' .. imgSizeStyle .. '></div>' .. |
||
+ | '<div class = "game-element-bg-settings-size-sos game-element-imgObject absolute"' .. imgSizeStyle .. '>' .. file(p.getIcon(icon), "link=" .. icon, "center") .. '</div>' .. |
||
+ | '</div>' |
||
+ | elseif table.contains({"addon", "item", "power", "offering"}, imgType) then |
||
+ | --rarity = (rarity and variousLogic.rarity[rarity].name:lower()) or string.replace((params and (params.rarity and (tonumber(params.rarity) and variousLogic.rarity[params.rarity].name) or params.rarity) or variousLogic.rarity[1].name):lower(), space, dash) |
||
+ | addonContainer = (imgType == "addon" and "addon-container") or cstr.empty |
||
+ | imgSize = imgSize or 128 |
||
+ | if isIconLink then |
||
+ | local ilOffset = (imgSize / 2) |
||
+ | local addonMarkerScale = 50 * (imgSize / 128) |
||
+ | return |
||
+ | '<span style="display:inline-flex; position:relative; height: ' .. ilOffset .. 'px; width: ' .. imgSize .. 'px">' .. |
||
+ | '<span class = "margin-auto" style = "display: inline-flex; position: absolute; top: -6px; max-width: ' .. imgSize .. 'px;' .. align ..'">' .. |
||
+ | '<span class = "game-element-container ' .. addonContainer .. ' flex relative">' .. |
||
+ | '<span class = "game-element-bg-settings game-element-bg-settings-size-perk ' .. rarity .. '-' .. (table.contains({"item", "offering"}, imgType) and imgType or "item") .. '-element" ' .. imgSizeStyle .. '></span>' .. |
||
+ | ((imgType == "addon" and '<span class = "game-element-bg-settings game-element-bg-settings-size-perk addon-marker addon-marker-il absolute" style = "scale: ' .. addonMarkerScale .. '%;"></span>') or cstr.empty) .. |
||
+ | '<span class = "game-element-bg-settings-size-sos absolute" ' .. imgSizeStyle .. '>' .. file(p.getIcon(icon), "link=" .. icon, "300px") .. '</span>' .. |
||
+ | '</span>' .. |
||
+ | '</span>' .. |
||
+ | '</span>' |
||
+ | else |
||
+ | return |
||
+ | '<div class = "margin-auto" style = "max-width: ' .. imgSize .. 'px; scale: calc(' .. imgSize .. '/128); ' .. align ..'">' .. |
||
+ | '<div class = "game-element-container ' .. addonContainer .. ' flex relative">' .. |
||
+ | '<div class = "game-element-bg-settings game-element-bg-settings-size-perk ' .. rarity .. '-' .. (table.contains({"item", "offering"}, imgType) and imgType or "item") .. '-element"></div>' .. |
||
+ | ((imgType == "addon" and '<div class = "game-element-bg-settings game-element-bg-settings-size-perk addon-marker absolute"></div>') or cstr.empty) .. |
||
+ | '<div class = "game-element-bg-settings-size-sos absolute">' .. file(p.getIcon(icon), "link=" .. icon, "300px") .. '</div>' .. |
||
+ | '</div>' .. |
||
+ | '</div>' |
||
+ | end |
||
+ | end |
||
+ | end |
||
+ | |||
+ | function p.resolvePageHeader(params) |
||
+ | return nil |
||
+ | end |
||
return p |
return p |
Latest revision as of 05:32, 17 April 2024
Colour test
An extremely (order: 1
colour: 2) long (order: 2
colour: 6) string (order: 3
colour: 8) that (order: 4
colour: 4) serves (order: 5
colour: 3) as a test (order: 6
colour: 10) string (order: 7
colour: 11) for (order: 8
colour: 12) automatic (order: 9
colour: 14) and (order: 10
colour: 15) ordered (order: 11
colour: 21) coloured (order: 12
colour: 22) text (order: 13
colour: 25) that (order: 14
colour: 26) colour-highlights (order: 15
colour: 9) important (order: 16
colour: 27) values (order: 17
colour: 28) on our (order: 18
colour: 29) beautiful (order: 19
colour: 13) Dead (order: 20
colour: 31) by Daylight (order: 21
colour: 32) ...
colour: 2) long (order: 2
colour: 6) string (order: 3
colour: 8) that (order: 4
colour: 4) serves (order: 5
colour: 3) as a test (order: 6
colour: 10) string (order: 7
colour: 11) for (order: 8
colour: 12) automatic (order: 9
colour: 14) and (order: 10
colour: 15) ordered (order: 11
colour: 21) coloured (order: 12
colour: 22) text (order: 13
colour: 25) that (order: 14
colour: 26) colour-highlights (order: 15
colour: 9) important (order: 16
colour: 27) values (order: 17
colour: 28) on our (order: 18
colour: 29) beautiful (order: 19
colour: 13) Dead (order: 20
colour: 31) by Daylight (order: 21
colour: 32) ...
Colour Table
Colouring OrderIn case of highlighting values and modifier values within a section, there is a specific order for the colours used to do so, which originates from the original Perk descriptions and is represented in the first four colours:
The above documentation is transcluded from Module:Utils/doc. (edit | history) |
The above documentation is transcluded from Module:Utils/doc. (edit | history)
local p = {}
local mathOps = require("Module:MathOps")
local str = require("Module:Strings")
local frame = mw.getCurrentFrame()
local _brigtnessTreshold = 0.36
local _isValidFileNameCounter = 0
local _colorOrder = {2, 6, 8, 4, 3, 10, 11, 12, 14, 15, 21, 22, 25, 26, 9, 27, 28, 29, 13, 31, 32}
local strings = {
--genders, not currently used
male = "Male",
female = "Female",
transmale = "Trans Male",
transfemale = "Trans Female",
nonhuman = "Not applicable (not human)",
undefined = "Undefined",
--charNotFound = "Character not found!",
patch = "Patch",
tabberHeaderNotFound = "Header not found",
category = "Category", --used for [[Category:...]]
boxTypeNotFound = b("Dynamic box is missing type!"),
notSpecified = "notSpecified", --used in IconLink for assemble machine function
clrTestString = "An extremely long string that serves as a test string for automatic and ordered coloured text that colour-highlights important values on our beautiful Dead by Daylight wiki. For this testing purposes we consider only 3-characters or longer words",
notEnoughColors = "no more colours in order",
}
p.strings = strings
local months = {
January = "January",
February = "February",
March = "March",
April = "April",
May = "May",
June = "June",
July = "July",
August = "August",
September = "September",
October = "October",
November = "November",
December = "December"
}
local days = {
Monday = "Monday",
Tuesday = "Tuesday",
Wednesday = "Wednesday",
Thursday = "Thursday",
Friday = "Friday",
Saturday = "Saturday",
Sunday = "Sunday"
}
----------------------------------------------
p.timeFormat1 = "%d.%m.%Y" -- 5.9.2022
p.timeContants = {
second = 1,
minute = 60, --in seconds
hour = 3600,
day = 86400,
week = 604800
}
local timeContants = p.timeContants
--Bridge to Language Module, as more appropriate place to have this function
function p.lang()
return require("Module:Languages").lang()
end
--If params will be passed {...} then index should equals 0, so this list will be passeb back: ((index == 0 and params.args) or params)
function p.resolveParameter(param, index, returnCanBeNil)
local retArg
if type(param) == "table" then retArg = ((index == 0 and param.args) or (param.args and param.args[(index or 1)] and p.replaceSpecialCharacters(param.args[(index or 1)]))) or nil end --if parameter is passed from wiki, not other function
if retArg == nil and type(param) == "table" and param.args ~= nil and next(param.args) == nil and not returnCanBeNil then retArg = getParamOrPageName() end --param.args ~= nil and next(param.args): this means that params.args is not nil but empty table
if retArg == nil and not returnCanBeNil then retArg = getParamOrPageName(param, index, false) end --if parameter was passed directly or not at all
if retArg ~= nil and type(param) == "string" then retArg = p.replaceSpecialCharacters(retArg) end --final processing
return retArg
end
function getParamOrPageName(param, index, returnCanBeNil)
if param and
(type(param) ~= types.table or
(type(param) == types.table and not param.args))
then
return param
else
local pageName = pageName or mw.title.getCurrentTitle().text
local pageParts = string.split(pageName, '/') -- remove page suffixes such as language codes
if #pageParts > 1 then
_unofficialLang = pageParts[#pageParts]
return pageParts[#pageParts - 1] --take second to last part of page name
end
return pageParts[1] --if there is no other part than the name itself
end
end
--translates string containing #1# #2# ... to individual strings (resp. words) contained in dyntable
function p.getDynamicString(dynTable, templateString)
--dynTable = {"Testing Chapter", nil, "DLC Str World", nil, 999};
--templateString = "Retracted #1# #2# #3# #4# #5# #6# #7# #8#"
local skipList = {skip = {}, spans = {}}
local spanRegex = "(<span .-</span>)" --mostly clr case
local spanSkipRegex = "&&span" --it needs to use a DIFFERENT symbol than hashtag (#) as the style of spans can contain one due to colorhex colour value
local skipRegex = "#(.-)#" --skip anything inside hashtags
if type(dynTable) == types.string then --is parameter is string then just split it
dynTable = string.split(dynTable)
elseif type(dynTable) == types.table then
local tmp = {}
local skipCounter = 1
local spanCounter = 1
for i, el in pairs(dynTable) do
if el:find(spanRegex) then --if you find any spans to skip then ...
for m in el:gmatch(spanRegex) do --let's find spans (and pull them out) to skip them with splitting by "space"
table.insert(skipList.spans, m)
el = el:gsub(m, spanSkipRegex .. spanCounter)
spanCounter = spanCounter + 1
end
end
--finding any strings DOES NOT need to be first as span regex WILL NOT create elements matching this regex
if el:find(skipRegex) then --if you find any string to skip then ...
for m in el:gmatch(skipRegex) do
table.insert(skipList.skip, m)
el = el:gsub(skip(m:gsub('%W','%%%1')), "#skip" .. skipCounter) --We have to escape special characters as the string is being used as a pattern and for an instance "[[ ]]" are magic chars for LUA patterns
skipCounter = skipCounter + 1
end
end
table.insert(tmp, string.split(tostring(el))) --if parameter is table, split all strings inside
end
dynTable = table.flatten(tmp) --format from table.insert is {{"splitted", "string"}, {"splitted", "another", "string"}}, so it's needed to be flattened
--mw.log(mw.dumpObject(dynTable))
end
if templateString then
for m in templateString:gmatch("#(%d+)#") do --replace all #1# occurences
local currentRegexString = "#" .. tonumber(m) .. "#"
if tonumber(m) <= #dynTable then
templateString = templateString:gsub(currentRegexString, dynTable[tonumber(m)])
else
templateString = templateString:gsub(currentRegexString, cstr.empty) --remove all remaining marks
end
end
else
templateString = table.join(dynTable, space)
end
templateString = string.trim(templateString)
--lg(templateString)
for m in templateString:gmatch("#skip(%d)") do --now we put spans back into string
local currentRegexString = "#skip" .. tonumber(m)
templateString = templateString:gsub(currentRegexString, skipList.skip[tonumber(m)])
end
for m in templateString:gmatch(spanSkipRegex .. "(%d)") do --now we put spans back into string
local currentRegexString = spanSkipRegex .. tonumber(m)
templateString = templateString:gsub(currentRegexString, skipList.spans[tonumber(m)])
end
--mw.log(templateString)
return templateString
end
--------------------------------------------------------------------------------
function p.getCount(subject)
local list
subject = p.resolveParameter(subject)
--if you have another list just add it into a list then call appropriate function
if subject == "map" then return p.getMapsCount()
elseif subject == "realm" then list = realms
elseif subject == "killer" then list = killers
elseif subject == "survivor" then list = survivors
elseif subject == "dlc" then list = dlcs
elseif subject == "chapter" then return p.getCountDlcType(1)
elseif subject == "paragraph" then return p.getCountDlcType(2)
elseif subject == "clothing" then return p.getCountDlcType(3)
elseif subject == "ost" then return p.getCountDlcType(4)
elseif subject == "character" then return p.getCountDlcType(5)
elseif subject == "ccy" then return getCountCCY(true)
elseif subject == "ccy-gc" then return getCountCCY(true) --redundant option to keep convention
elseif subject == "ccy-rc" then return getCountCCY(false)
elseif subject == "killerPerk" then return getPerksCount('K')
elseif subject == "survPerk" then return getPerksCount('S')
else return 0
end
local x = 0
for _, item in ipairs(list) do if item.skip or item.retired then x = x + 1 end end
return #list - x
end
function p.getMapsCount()
local data = require("Module:Datatable")
local result = 0
for _, map in ipairs(data.maps) do
if not (map.skip or map.retired) then
if map.variations then
for _, var in ipairs(map.variations) do
if not (map.skip or map.retired) then
result = result + 1
end
end
else
result = result + 1
end
end
end
return result
end
function p.getCountDlcType(type)
local count = 0
for _, dlc in ipairs(dlcs) do
if dlc.category == type and not dlc.skip then count = count + 1 end
end
return count
end
function getPerksCount(charType)
local perks = mw.loadData("Module:Datatable/Loadout" .. p.lang()).perks
local count = 0
for _, perk in ipairs(perks) do
if perk.charType == charType and not perk.unused then count = count + 1 end
end
return count
end
function getCountCCY(gc)
local list = ccy
local i = 0
while list[i + 1] and list[i + 1].gc == gc do i = i + 1 end
return i
end
function isRealCcy(ccyId)
local data = require("Module:Datatable" .. p.lang())
for _, currency in ipairs(data.ccy) do
if currency.id == ccyId then return not currency.gc end
end
return false
end
function p.getCcyById(id)
for _, currCcy in ipairs(ccy) do
if currCcy.id == id then return currCcy end
end
return nil
end
function p.getMaxId(tab)
result = 0
for _, item in ipairs(tab) do
result = (item.id > result and item.id) or result
end
return result
end
-- Function allowing for consistent treatment of boolean-like wikitext input.
-- It works similarly to the template {{yesno}}.
function p.bool(val, default)
-- If your wiki uses non-ascii characters for any of "yes", "no", etc., you
-- should replace "val:lower()" with "mw.ustring.lower(val)" in the
-- following line.
val = type(val) == 'string' and val:lower() or val
if val == nil then
return nil
elseif val == true
or val == 'yes'
or val == 'y'
or val == 'true'
or val == 't'
or val == 'on'
or tonumber(val) == 1
then
return true
elseif val == false
or val == 'no'
or val == 'n'
or val == 'false'
or val == 'f'
or val == 'off'
or tonumber(val) == 0
then
return false
else
return default
end
end
--Example usage:
--amount = 1333444.1
--print(format_num(amount,2))
--print(format_num(amount,-2,"US$"))
--amount = -22333444.5634
--print(format_num(amount,2,"$"))
--print(format_num(amount,2,"$","()"))
--print(format_num(amount,3,"$","NEG "))
--Output:
--1,333,444.10
--US$1,333,400
---$22,333,444.56
--($22,333,444.56)
--NEG $22,333,444.563
function p.formatNum(amount, decimal, prefix, neg_prefix)
return p.format_num(amount, decimal, prefix, neg_prefix)
end
function p.format_num(amount, decimal, prefix, neg_prefix)
local str_amount, formatted, famount, remain
decimal = decimal or 2 -- default 2 decimal places
neg_prefix = neg_prefix or "-" -- default negative sign
famount = math.abs(mathOps.round(amount,decimal))
famount = math.floor(famount)
remain = mathOps.round(math.abs(amount) - famount, decimal)
-- comma to separate the thousands
formatted = p.commaFormat(famount)
-- attach the decimal portion
if (decimal > 0) then
remain = string.sub(tostring(remain),3)
formatted = formatted .. "." .. remain ..
string.rep("0", decimal - string.len(remain))
end
-- attach prefix string e.g '$'
formatted = (prefix or "") .. formatted
-- if value is negative then format accordingly
if (amount<0) then
if (neg_prefix=="()") then
formatted = "("..formatted ..")"
else
formatted = neg_prefix .. formatted
end
end
return formatted
end
function p.commaFormat(amount)
local formatted = amount
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
if (k==0) then
break
end
end
return formatted
end
--Converting Arabic numbers to Roman
function p.toRomanNumerals(s)
local numbers = { 1, 5, 10, 50, 100, 500, 1000 }
local chars = { "I", "V", "X", "L", "C", "D", "M" }
s = tonumber(s)
if not s or s ~= s then return false end
if s == math.huge then return false end
s = math.floor(s)
if s <= 0 then return s end
local ret = cstr.empty
for i = #numbers, 1, -1 do
local num = numbers[i]
while s - num >= 0 and s > 0 do
ret = ret .. chars[i]
s = s - num
end
for j = 1, i - 1 do
local n2 = numbers[j]
if s - (num - n2) >= 0 and s < num and s > 0 and num - n2 ~= n2 then
ret = ret .. chars[j] .. chars[i]
s = s - (num - n2)
break
end
end
end
return ret
end
--Converting Roman numbers to Arabic
function ToNumeral(roman)
local Num = { ["M"] = 1000, ["D"] = 500, ["C"] = 100, ["L"] = 50, ["X"] = 10, ["V"] = 5, ["I"] = 1 }
local numeral = 0
local i = 1
local strlen = string.len(roman)
while i < strlen do
local z1, z2 = Num[ string.sub(roman,i,i) ], Num[ string.sub(roman,i+1,i+1) ]
if z1 < z2 then
numeral = numeral + ( z2 - z1 )
i = i + 2
else
numeral = numeral + z1
i = i + 1
end
end
if i <= strlen then numeral = numeral + Num[ string.sub(roman,i,i) ] end
return numeral
end
function resolveNameWithRomanNumbers(str)
local index = string.find(str, " [^ ]*$") + 1
local romanNumber = (string.sub(str, index))
local result
if string.match(romanNumber, "[MDCLXVI]+$") then --finding ONLY Roman letters in last "word"
result = str
else
result = str
end
return result
end
function p.capitalizeName(str) return p.CapitalizeName(str) end
function p.CapitalizeName(str) --deprecated name
--mw.log(string.gsub(" "..str, "[%s%-]%a", string.upper):sub(2))
--return string.gsub(" "..str, "[%s%-]%a", string.upper):sub(2) --original version
return string.gsub(" "..str:lower(), "[%s%-]%a", string.upper):sub(2)
end
function p.firstLetterLower(str) return p.FirstLetterLower(str) end
function p.FirstLetterLower(str) --deprecated name
return str:sub(1, 1):lower() .. str:sub(2)
end
function p.firstLetterUpper(str) return p.FirstLetterUpper(str) end
function p.FirstLetterUpper(str) --deprecated name
return str:sub(1,1):upper() .. str:sub(2)
end
--[[function p.fixDiacritics(str)
local letterSequences = {
["195"] = {fix = "â", sequence = {"162"}}
}
local fixedSeqsOffset = 0
local currentLen = #str + fixedSeqsOffset
local i = 1
while i <= currentLen do
local ascii = tostring(string.byte(str:sub(i,i)) or 0)
for startSequence, seqData in pairs(letterSequences) do
if ascii == startSequence then
local foundSequence = true
for j, seqCode in ipairs(seqData.sequence) do
local seqAscii = tostring(string.byte(str:sub(i+j,i+j)))
if seqAscii ~= seqCode then
foundSequence = false
break
end
end
if foundSequence then
fixedSeqsOffset = i - (i + #seqData.sequence)
currentLen = #str + fixedSeqsOffset
-- #seqData.sequence + 1 = number of ascii codes that needs to be replaced, the sequence list + the initial ascii code, i.e. the key value from letterSequences
str = str:sub(1, i - 1) .. seqData.fix .. str:sub(i + #seqData.sequence + 1)
end
end
end
i = i + 1
end
return str
end]]
function p.RemoveSpecialCharacters(str, full, replaceDiacritics)
str = string.gsub(str, "'", "")
str = string.gsub(str, "®", "")
str = string.gsub(str, "™", "")
str = string.gsub(str, ":", "")
str = string.gsub(str, "!", "") --perks
str = string.gsub(str, "?", "") --achievements
str = string.gsub(str, "/", "") --achievements
str = string.gsub(str, "%&", "And") --probably can be changed to lower: "and"
if replaceDiacritics then
str = p.replaceDiacritics(str, full)
end
return str
end
function p.replaceDiacritics(str, full)
--Diacritics
specialLettersLight = {
["A"] = {"À", "Á", "Â", "Ã", "Ä"},
["a"] = {"à", "á", "â", "ã", "ä"},
["e"] = {"è", "é", "ê", "ë", "ě"},
["O"] = {"Ò", "Ó", "Ô", "Õ", "Ö"},
["o"] = {"ò", "ó", "ô", "õ", "ö"}
}
specialLetters = {
["A"] = {"À", "Á", "Â", "Ã", "Ä"},
["a"] = {"à", "á", "â", "ã", "ä"},
["E"] = {"È", "É", "Ê", "Ë", "Ě"},
["e"] = {"è", "é", "ê", "ë", "ě"},
["I"] = {"Ì", "Í", "Î", "Ñ", "Ï"},
["i"] = {"ì", "í", "î", "ñ", "ï"},
["O"] = {"Ò", "Ó", "Ô", "Õ", "Ö"},
["o"] = {"ò", "ó", "ô", "õ", "ö"},
["U"] = {"Ù", "Ú", "Û", "Ů", "Ü"},
["u"] = {"ù", "ú", "û", "ů", "ü"},
["Y"] = { "Ý", "Ÿ"},
["y"] = { "ý", "ÿ"}
}
--Originally it was grouped by set [ÀÁ], however there is some sort of bug there that makes it not working
--str = string.gsub(str, "[ÁÂ]", "A")
--if debugRun then mw.log(mw.dumpObject(specialLetters)) end
for letter, row in pairs(((full and specialLetters) or specialLettersLight)) do
for _, special in ipairs(row) do
str = string.replace(str, special, letter)
end
end
return str
end
function p.replaceSpecialCharacters(name)
local charList = {
["'"] = ''',
["&"] = '&'
}
if name == nil then error("Name parameter is empty, but it shouldnt?") end
for repl, sChar in pairs(charList) do
name = string.gsub(tostring(name), sChar, repl)
end
return name
end
function p.pageExists(name)
return (not name == cstr.empty) or mw.title.new(name).exists
end
function p.isValidFileName(name, extension)
extension = extension or "png"
name = p.RemoveSpecialCharacters(name)
lg("Validation check, Name: " .. name)
_isValidFileNameCounter = _isValidFileNameCounter + 1
--mw.log("Counter:" .. tostring(_isValidFileNameCounter) .. ' - ' .. cstr.media .. name .. dot .. extension)
return not (name == cstr.empty or not mw.title.new(cstr.media .. name .. dot .. extension).exists)
end
function p.resolveFileName(str, keepSpaces, removeDiacritics)
keepSpaces = keepSpaces or false
local result = ""
result = string.lower(str)
--mw.log(result)
result = p.RemoveSpecialCharacters(result, keepSpaces, removeDiacritics)
--mw.log(result)
result = p.capitalizeName(result)
if not keepSpaces then
result = string.gsub(result, "[ ]", "")
end
--In future if there will be needed replace charactere such as "é" just add another substitution
--mw.log(result)
return result
end
function p.resolveImageName(name)
if mw.title.new(cstr.media .. name .. dot .. cstr.png).exists then return name .. dot .. cstr.png end
if mw.title.new(cstr.media .. name .. dot .. cstr.jpg).exists then return name .. dot .. cstr.jpg end
if mw.title.new(cstr.media .. name .. dot .. cstr.gif).exists then return name .. dot .. cstr.gif end
return name .. dot .. cstr.png
end
function p.GetDisplayName(item)
return (item.tName or item.name)
end
function p.IconLink(icon, pageLink, displayText, background)
local result = cstr.empty
background = background or p.resolveParameter(icon, "bg", true) or p.resolveParameter(icon, 4, true)
background = p.bool(background == nil or background)
displayText = displayText or p.resolveParameter(icon, 3, true)
pageLink = pageLink or p.resolveParameter(icon, 2, true)
local linkless = p.resolveParameter(icon, "linkless", true) or pageLink == "linkless" --linkless as a second parameter should be deprecated
icon = p.resolveParameter(icon)
local iconObject, pluralType = p.getIconObject(icon)
if pluralType then --if automatic plural is detected, then links should redirect to singular form
pageLink = pageLink or iconObject.name
displayText = icon --since automatic plurals don't imitate 3-parameters scenario we have to set the display text to be same as the first parameter (icon)
end
local iconFile = iconObject.iconFile
local filename = cstr.file .. iconFile .. tl .. "link=" .. (pageLink or icon)
local text = (displayText and pageLink and pageLink .. tl .. displayText) or pageLink or icon --cstr.empty
--local boxDesc = cstr.empty
if pageLink == "img" then
text = cstr.empty
filename = cstr.file .. iconFile .. tl .. "link=" .. icon
end
if not linkless and text ~= cstr.empty then
text = link(text)
--boxDesc = getBoxDescription(pageLink or icon) //hover box, currently disabled
elseif linkless or pageLink == "linkless" then
text = (displayText or icon)
end
local imageFile
if background and iconObject.assembly then
local loLogic = require("Module:Loadout" .. p.lang())
--this needs to (should) be done sophisticated way
local imgType =
(iconObject.iconFile:lower():find("addon") and "addon") or
(iconObject.iconFile:lower():find("items") and "item") or
(iconObject.iconFile:lower():find("favors") and "offering") or
(iconObject.iconFile:lower():find("powers") and "power") or
(iconObject.iconFile:lower():find("perks") and "perk") or
strings.notSpecified
--if iconObject.iconFile:lower():find("favors") then imgType = "offering" end
local loObj = (loLogic["get" .. p.capitalizeName(imgType) .. "ByName"] and loLogic["get" .. p.capitalizeName(imgType) .. "ByName"](iconObject.name)) or nil
imageFile = p.assembleImage(imgType, icon, 32, {rarity = (loObj and loObj.rarity) or 1, iconLink = true})
else
imageFile = link(filename, "64px") --iconObject.ilRes or "32px")
end
local tooltipText = link(filename, "96px", "link=")
--result = '<span class = "wrap-span pcView" style = "display:none;"><span class="box-span">' .. boxDesc .. '</span>' .. text .. file .. '</span>'
local padding = p.getPaddingsFromIcon(iconObject)
result = text ..
'<span class = "iconLink" style = "vertical-align: middle; padding: ' .. padding.top .. space .. padding.right .. space .. padding.bottom .. space .. padding.left .. '">' ..
'<span style = "display:none; width:' .. (iconObject.ilRes or "32px") .. semi .. 'max-height: ' .. (iconObject.ilRes or "32px") .. '" class = "pcView pcIconLink">' .. p.tooltip(imageFile, tooltipText, true, true, true) .. '</span>' ..
'<span class = "mobileView mobileIconLink">' .. link(filename, iconObject.ilRes or "32px") .. '</span>' ..
'</span>'
--mw.log(result)
return result
--return frame:expandTemplate{title = "IconLink", args = {icon, pageLink, displayText} }
end
function p.loadoutIconLink(icon, pageLink, displayText, params)
displayText = displayText or p.resolveParameter(icon, "displayText", true) or p.resolveParameter(icon, 3, true)
pageLink = pageLink or p.resolveParameter(icon, "link", true) or p.resolveParameter(icon, 2, true)
icon = p.resolveParameter(icon, "icon", true) or p.resolveParameter(icon, 1, true) or icon
local info = {
displayText = displayText or pageLink or icon,
link = pageLink or icon,
icon = icon
}
local iconObject, pluralType = p.getIconObject(info.icon)
local filename = cstr.file .. iconObject.iconFile .. tl .. "link=" .. (info.link or info.icon)
local tooltipText = link(filename, "96px", "link=") --default tooltip
if iconObject.iconFile:lower():find("perk") then
local loLogic = require("Module:Loadout" .. p.lang())
tooltipText = loLogic.getPerkPageTable(params.perk) or "WIP/Error:" .. params.perk.name
end
local imageFile = link(filename, "64px")
local padding = p.getPaddingsFromIcon(iconObject)
local result = link(info.displayText, info.link) ..
'<span class = "iconLink" style = "vertical-align: middle; padding: ' .. padding.top .. space .. padding.right .. space .. padding.bottom .. space .. padding.left .. '">' ..
'<span style = "display:none; width:' .. (iconObject.ilRes or "32px") .. semi .. 'max-height: ' .. (iconObject.ilRes or "32px") .. '" class = "pcView pcIconLink">' .. p.tooltip(imageFile, tooltipText, true, true, true) .. '</span>' ..
--'<span class = "mobileView mobileIconLink">' .. link(filename, iconObject.ilRes or "32px") .. '</span>' ..
'</span>'
return result
end
function p.getPaddingsFromIcon(icon)
return {
top = icon.paddingTop or 0,
right = icon.paddingRight or 0,
bottom = icon.paddingBottom or 0,
left = icon.paddingLeft or 0
}
end
function getBoxDescription(icon) --curently disabled
local ic = p.getIconObject(icon)
local result = cstr.empty
if ic.category == "Perks" then
result = '<h3>' .. icon .. '</h3><hr>'-- .. prkz.getPerkDescriptionByName(perk.name)
end
return result
end
function p.resolveTextColorByBackground(hexBgColor)
if(type(hexBgColor) == "table") then
hexBgColor = hexBgColor.args[1]
end
local red = tonumber(string.sub(hexBgColor, 1, 2), 16)
local green = tonumber(string.sub(hexBgColor, 3, 4), 16)
local blue = tonumber(string.sub(hexBgColor, 3, 4), 16)
local darkness = (0.299 * red + 0.587 * green + 0.114 * blue) / 255
--mw.log("Red: " .. red .." \t\tor\t Blue: " .. blue .. " | Green: " .. green)
--mw.log(darkness)
if(darkness < _brigtnessTreshold) then
return "white"
else
return "black"
end
end
function p.getPageTitle()
return mw.title.getCurrentTitle().text
end
---------------------------------------------------------------------------------
function p.getSumOfSizes(row)
local result = 0
local i = 1
while row[i] do
result = result + row[i][1] -- hardcoded first variable in table
i = i + 1
end
return result
end
--[[
function compSize(row1, row2)
local sum1 = row1.size
local sum2 = row2.size
if(type(sum1) == "table") then
sum1 = p.getSumOfSizes(sum1) --converting back from table to number
end
if(type(sum2) == "table") then
sum2 = p.getSumOfSizes(sum2)
end
if sum1 > sum2 then return true
elseif sum1 < sum2 then return false
else
if row1.realm < row2.realm then return true
elseif row1.realm > row2.realm then return false
else
return resolveNameWithRomanNumbers(row1.name) < resolveNameWithRomanNumbers(row2.name)
end
end
end
]]
function compName(row1, row2)
if row1.diacritics then
if row2.diacritics then
return p.RemoveSpecialCharacters(row1.name, true, true) < p.RemoveSpecialCharacters(row2.name, true, true)
else
return p.RemoveSpecialCharacters(row1.name, true, true) < row2.name
end
elseif row2.diacritics then
return row1.name < p.RemoveSpecialCharacters(row2.name, true, true)
end
return row1.name < row2.name
end
function compFilename(row1, row2)
return row1.filename < row2.filename
end
function compInt(row1, row2) -- 1, 2, 3
return row1 < row2
end
function compIntReversed(row1, row2) -- 3, 2, 1
return row1 > row2
end
function compIntDesc(row1, row2) -- 3, 2, 1
return row1 > row2
end
function compLevel(row1, row2)
return row1.level < row2.level
end
function compDlcCategory(row1, row2)
if row1.category == row2.category then
return row1.id < row2.id
end
return row1.category < row2.category
end
function compId(row1, row2)
return row1.id < row2.id
end
function compRealCcyFirst(row1, row2)
ccy1 = isRealCcy(row1.ccy)
ccy2 = isRealCcy(row2.ccy)
if (ccy1 and ccy2) or (not ccy1 and not ccy2) then --TRUE and TRUE or FALSE and FALSE
return row1.ccy < row2.ccy
elseif ccy1 and not ccy2 then --TRUE and FALSE
return true
else
return false
end
end
function compCharsKillersFirst(row1, row2)
char1 = row1.power ~= nil
char2 = row2.power ~= nil
if (char1 and char2) or (not char1 and not char2) then --TRUE and TRUE or FALSE and FALSE
return row1.id < row2.id
elseif char1 and not char2 then --TRUE and FALSE
return true
else
return false
end
end
function rarityAndName(row1, row2)
if row1.rarity and row2.rarity and row1.rarity < row2.rarity then
return true
elseif row1.rarity and row2.rarity and row1.rarity > row2.rarity then
return false
elseif row1.pieces and row2.pieces then
local row1Name = cstr.empty
local row2Name = cstr.empty
for _, piece in pairs(row1.pieces) do row1Name = piece.name break end
for _, piece in pairs(row2.pieces) do row2Name = piece.name break end
return row1Name < row2Name
else
return compName(row1, row2)
end
end
function p.sortByKeys(tableList) --only int, TODO check key type via pairs() fc
local result = {}
local orderedKeys = table.keys(tableList)
p.sortTable(orderedKeys, true)
for _, key in pairs(orderedKeys) do
result[key] = tableList[key]
end
return result
end
function p.sortByRDate(tableList, reversed)
table.sort(tableList,
function (row1, row2)
if p.toTimestamp(row1.rDate) < p.toTimestamp(row2.rDate) then
if reversed then
return false
else
return true
end
end
end
)
return tableList
end
function p.sortMapsBySize() --deprecated?
local m = require("Module:Datatable" .. p.lang())
--mw.log(mw.dumpObject(maps))
table.sort(maps,compSize)
--mw.log(mw.dumpObject(maps))
end
function p.sortItemsByName(tableList)
table.sort(tableList, compName)
end
function p.sortItemsBycompFilename(tableList) --used for charms (as perk object is not retrieved, only cosData record)
table.sort(tableList, compFilename)
end
function p.sortTable(tableList, reversed)
if reversed then
table.sort(tableList, compIntReversed)
else
table.sort(tableList, compInt)
end
end
function p.sortTableDesc(tableList)
table.sort(tableList, compIntDesc)
end
function p.sortDlcByCategory(tableList)
table.sort(tableList, compDlcCategory)
end
function p.sortPerksByLevel(tableList)
table.sort(tableList, compLevel)
end
function p.sortTableById(tableList)
table.sort(tableList, compId)
end
function p.sortRealCcyFirst(tableList)
table.sort(tableList, compRealCcyFirst)
end
function p.sortCharsKillersFirst(tableList)
table.sort(tableList, compCharsKillersFirst)
end
function p.sortCosmeticsByRarityAndName(tableList)
table.sort(tableList, rarityAndName)
end
---------------------------------------------------------------------------------
-- Time functions --
function p.today()
return os.time(os.date("!*t"))
end
function p.getYear(stamp) return p.getTimeAspect(stamp, "year") end
function p.getMonth(stamp) return p.getTimeAspect(stamp, "month") end
function p.getDay(stamp) return p.getTimeAspect(stamp, "day") end
function p.getHour(stamp) return p.getTimeAspect(stamp, "hour") end
function p.getMinute(stamp) return p.getTimeAspect(stamp, "minute") end
function p.getSecond(stamp) return p.getTimeAspect(stamp, "second") end
function p.getWeekday(stamp) return p.getTimeAspect(stamp, "weekday") end
function p.getTimeAspect(stamp, aspect) --aspect being selected time portion
if type(stamp) ~= 'number' then
stamp = p.toTimestamp(stamp)
end
local tag = '%c'
if aspect == "year" then tag = '%Y'
elseif aspect == "month" then tag = '%m'
elseif aspect == "day" then tag = '%d'
elseif aspect == "hour" then tag = '%H'
elseif aspect == "minute" then tag = '%M'
elseif aspect == "second" then tag = '%S'
elseif aspect == "weekday" then tag = '%w'
end
local result = os.date(tag, stamp)
return (aspect == "weekday" and tonumber(result)) or result
end
function p.addTime(portion, amount, stamp)
stamp = stamp or 0
if type(stamp) ~= 'number' then
stamp = p.toTimestamp(stamp)
end
local multiplier = timeContants[portion]
stamp = stamp + (amount * multiplier)
return stamp
end
function p.getTimeDiff(timestamp1, timestamp2) --First parameter is first in timeline, Second Parameter is later one
return os.difftime(timestamp2, timestamp1)
end
function p.getTimeDiffFormatting(seconds, unit)
return seconds / timeContants[unit]
end
function p.toTimestamp(datestamp)
if type(datestamp) == 'string' then
datestamp = p.GetDatetime(datestamp)
end
return os.time(datestamp) --if datestamp = nil, os.time() returns current datetime stamp
end
function p.toDate(timestamp, timeFormat)
return os.date(timeFormat or '%d %B %Y', timestamp) --21 October 2021
end
function p.resolveDateTime(datetime, skipDay) --with weekday
local matchYear = "^(..)%.(..)%.(%d%d%d%d)$"
local matchMonth = "^(..)%.(%d%d)%.(....)$"
local matchDay = "^(%d%d)%.(..)%.(....)$"
local year, month, day, dayName = false
local rDate = os.time(p.GetDatetime(datetime))
if string.find(datetime, matchYear) then
year = os.date("%Y", rDate)
end
if string.find(datetime, matchMonth) then
month = os.date("%B", rDate)
end
if string.find(datetime, matchDay) then
day = os.date("%d", rDate)
dayName = os.date("%A", rDate)
end
--(day and month) is to avoid showing a day as the function sets the month to January (first month) by default, if the month is not provided
--day part: ((day and month and day .. space) or cstr.empty)
--month part: ((month and months[month] .. space) or cstr.empty)
--year part: year
--dayName part: ((day and month and not skipDay and space .. brackets(days[dayName])) or cstr.empty)
return ((day and month and day .. space) or cstr.empty) .. ((month and months[month] .. space) or cstr.empty) .. year .. ((day and month and not skipDay and space .. brackets(days[dayName])) or cstr.empty)
end
function p.isFullDateTime(datestamp) return p.IsFullDateTime(datestamp) end
function p.IsFullDateTime(datestamp)
local day, month, year = datestamp:match("^(..)%.(..)%.(....)$")
if month == "##" or day == "##" or year == "####" then return false end
return true
end
--convertToTimeVersion = normally, return table counts as 1.1.1970 (with day = 1, month = 1 and year = 1970).
--But if we want to have time stamp (like remaining time) these default values add 1 day, 1 month and 1970 years to the countdown table (Time stamp version table)
function p.GetDatetime(datestamp, convertToTimeVersion)
local result = {}
if type(datestamp) == types.string then
local day, month, year, hour, min, sec = datestamp:match("^(..)%.(..)%.(%d%d%d%d) ?(%d?%d?):?(%d?%d?):?(%d?%d?)$")
if month == "##" then
month = 1
result.fakeMonth = true
end
if day == "##" then
day = 1
result.fakeDay = true
end
result.year = tonumber(year)
result.month = tonumber(month)
result.day = tonumber(day)
result.hour = tonumber((hour ~= cstr.empty and hour) or 0)
result.min = tonumber((min ~= cstr.empty and min) or 0)
result.sec = tonumber((sec ~= cstr.empty and sec) or 0)
result.timestamp = p.toTimestamp(result)
local tempDateObj = os.date("!*t", os.time(result))
result.yday = tempDateObj.yday --year day included
result.wday = p.correctWeekday(tempDateObj).wday --weekDay included
result.week = p.getWeekNumber(result.timestamp)
result.quarter = math.ceil(result.month / 3)
result.dst = tempDateObj.isdst
elseif type(datestamp) == types.number then
result = os.date("!*t", datestamp)
if(convertToTimeVersion) then
result.day = result.day - 1 --we have to subtract it, not set it, as the datetime stamp can reach to days and months
result.month = result.month - 1
result.year = result.year - 1970
end
result = p.correctWeekday(result)
result.timestamp = datestamp
end
return result
end
--.wday is indexed 1 - 7, Sunday being 1 => https://www.lua.org/manual/5.3/manual.html#:~:text=wday%20(weekday%2C%201%E2%80%937%2C%20Sunday%20is%C2%A01)
--%w starts 0 - 6, Sunday being 0 => https://www.lua.org/pil/22.1.html#:~:text=%25w,6%20%3D%20Sunday%2DSaturday%5D
--following row synchronises the convention with '%w' and shifting it to 1 - 7 starting Monday, as for the rest of Wiki
function p.correctWeekday(datetable)
datetable.wday = (datetable.wday == 1 and 7) or datetable.wday - 1
return datetable
end
--ISO: https://en.wikipedia.org/wiki/ISO_week_date
--If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in W01. If it is on a Friday, it is part of W53 of the previous year.
--If it is on a Saturday, it is part of the last week of the previous year which is numbered W52 in a common year and W53 in a leap year.
--If it is on a Sunday, it is part of W52 of the previous year.
function p.getWeekNumber(dateStamp) --in milliseconds, os.time()
firstWeekDayOfYear = tonumber(os.date("%w", os.time({year = os.date("%Y", dateStamp), month = 1, day = 1})))
firstWeekOfYear = tonumber(os.date("%W", os.time({year = os.date("%Y", dateStamp), month = 1, day = 1})))
-- ATTENTION as per description above some years have first week 01
-- because all first weeks of year that starts with Sunday, Saturday, Friday or Thursday must be shifted
if firstWeekDayOfYear > 0 and firstWeekDayOfYear < 4 and firstWeekOfYear == 0 then
return tonumber(os.date("%W", dateStamp)) + 1
else
return tonumber(os.date("%W", dateStamp))
end
end
function p.getDatePart(datestamp, part)
local day, month, year = datestamp:match("^(..)%.(..)%.(....)$")
if year == "####" then month = false end
if month == "##" then month = false end
if day == "##" then day = false end
if part == "day" then return day
elseif part == "month" then return month
elseif part == "year" then return year
end
end
--------------------------------------------------------------------------------
function p.regularReplace(reggedString, regexTable)
local result = cstr.empty
for key, value in pairs(regexTable) do
reggedString = reggedString:gsub(key, value)
end
return reggedString
end
function p.getIcon(icon)
return p.getIconObject(icon).iconFile
end
function p.getIconObject(icon)
icon = p.resolveParameter(icon)
local icons = mw.loadData("Module:Datatable/Icons" .. p.lang()).icons --mw.loadData
local pluralRegex = "^(.+)s$"
local plural2Regex = "^(.+)es$"
local plural3Regex = "^(.+)ies$"
local plural = icon:match(pluralRegex)
local plural2 = icon:match(plural2Regex) --Toolboxes => Toolbox
local plural3 = icon:match(plural3Regex) --Bodies => Bod
local result = getIconElement(icons, icon, true)
if result then
return result
elseif plural and icons[plural] ~= nil then --Pallets => Pallet
return getIconElement(icons, plural), 1
elseif plural2 and icons[plural2] ~= nil then --Toolboxes => Toolbox
return getIconElement(icons, plural2), 2
elseif plural3 and icons[plural3 .. "y"] ~= nil then --Bodies => Body
return getIconElement(icons, plural3 .. "y"), 3
else
return getIconElement(icons, icon)
end
end
function getIconElement(icons, icon, returnNil)
if type(icons[icon]) == types.table then
local result = table.copy(icons[icon])
result.name = icon
return result
elseif returnNil then
return nil
elseif icons[icon] == nil then
return icons["Unknown QuestionMark"] or icons[1]
else
return getIconElement(icons, icons[icon])
end
end
--************************* survivors & killers ******************************--
function p.getCharacterById(id, charTable)
for _, character in ipairs(charTable) do
if character.id == id then return character end
end
end
function p.getCharacter(object)
local data = require("Module:Datatable" .. p.lang())
if object.killer or object.charType == 'K' then
return p.getCharacterById(object.killer or object.character, data.killers)
else
return p.getCharacterById(object.survivor or object.character, data.survivors)
end
end
--function returns appropriate character name that should be compatible with IL.
--IL itself is not executed to have option to customise the IL call
function p.resolveCharacterIconLinkName(owner)
return (owner and owner.multiName and owner.realName) or (not p.isKiller(owner) and owner.shortName) or owner.name or false
end
function p.resolveCharacterPortraitFileName(character, maxId)
local fileConst = "_charPreview_portrait"
local isKiller = p.isKiller(character)
local unknownChar = (isKiller and "UnknownKiller") or "UnknownSurvivor"
local fileName
fileName = p.getFileNameFromTableById(character.id, (isKiller and killerImages) or survivorImages, "preview") --get custom name from table
if fileName == cstr.empty or not p.isValidFileName(fileName) then --K/S{ID}_charPreview_portrait
fileName = p.getCharacterIdentifier(character) .. fileConst
end
if (maxId and (character.id >= maxId - 1 and not p.isValidFileName(fileName)) or (not maxId and not p.isValidFileName(fileName))) then --File not Found; maxId - 1 to consider last two characters, not only the last one
fileName = unknownChar .. fileConst
end
--mw.log(fileName)
return fileName
end
function p.getCharacterIdentifier(character)
return ((p.isKiller(character) and 'K') or 'S') .. string.format("%02d", character.id)
end
function p.getFileNameFromTableById(id, fileTable, field)
--mw.log(id)
for j, sImage in ipairs(fileTable) do
if sImage.id == id and sImage[field] ~= nil then
return sImage[field]
end
end
return cstr.empty
end
--Not currently used
function p.resolveGender(abbr)
if abbr == 'M' then return strings.male
elseif abbr == 'F' then return strings.female
elseif abbr == 'N' then return strings.nonhuman
elseif abbr == 'M/F' then return strings.male .. comma .. strings.female
elseif abbr == 'F/M' then return strings.female .. comma .. strings.male
elseif abbr == 'TM' then return strings.transmale
elseif abbr == 'TF' then return strings.transfemale
else return strings.undefined
end
end
--classes in form: "sortable, class2, class3 ,..."
function p.wrapBasicTable(content, classes, styles, skipNtl)
return '{| class = ' .. quotes('wikitable' .. ((classes and space .. p.getTableClasses(classes)) or cstr.empty)) .. ((styles and space .. 'style = ' .. quotes(styles)) or cstr.empty) .. nl ..
(not skipNtl and ntl .. nl or cstr.empty) .. content ..'|}'
end
function p.wrapTable(content, classes, styles, skipNtl)
return (classes and '{| class = ' .. quotes((p.getTableClasses(classes))) or cstr.empty) .. ((styles and space .. 'style = ' .. quotes(styles)) or cstr.empty) .. nl ..
(not skipNtl and ntl .. nl or cstr.empty) .. content ..'|}'
end
function p.getTableClasses(classes)
classes = p.resolveParameter(classes, 1)
local unknownClass = "unknownClass"
if not classes then return unknownClass end
if type(classes) == "string" then
classes = classes:gsub(", ", space):gsub(",", space)
else --todo type(classes) == "table"
return unknownClass
end
return classes
end
function p.getCharacterByName(name)
name = name or p.resolveParameter(name)
local str = require("Module:Datatable" .. p.lang())
for _, surv in ipairs(survivors) do
if surv.shortName == name or surv.name == name then
local survivor = table.copy(surv)
survivor.isKiller = false
return survivor
end
end
for _, killer in ipairs(killers) do
if killer.shortName == name or killer.realName == name or the(killer) .. killer.name == name or killer.name == name then
local klr = table.copy(killer)
klr.isKiller = true
return klr
end
end
return nil
end
--deprecated
function p.getCharsByDlc(dlc, charType)
local data = require("Module:Datatable" .. p.lang())
local result = {}
local listTable = (charType == 'S' and data.survivors) or (charType == nil and data.survivors) or data.killers --if the charType is not set then set the table by default to survivors
for _, character in ipairs(listTable) do
if character.dlc == dlc.id then
result[#result + 1] = character
end
end
if charType ~= nil then --if the charType is set, we need loop through only one table. Otherwise charType wasn't set in order to retrieve both types of characters from DLC
return result
end
for _, character in ipairs(data.killers) do --since the default table is survivor, we can hardcode killer table as a second table
if character.dlc == dlc.id then
result[#result + 1] = character
end
end
return result
end
function p.getCharacterFirstName(character)
character = p.resolveParameter(character)
if not character.name then
character = p.getCharacterByName(character)
end
local regex = '([^ ]*) ?' --%a doesn't consider diacritics such as É (for instance: Élodie)
local isKiller = p.isKiller(character)
local text = (isKiller and character.name) or character.shortName or character.name --if the char is killer then use their name, otherwise it's survivor, thus use shortName or name
if isKiller then return text end --if it's killer, then their nickname is already what it's supposed to return
if regex and text:gmatch(regex) then
for m in text:gmatch(regex) do
return m --return first occurence before potential space, otherwise return whole string
end
end
return "Name not found"
end
function p.replaceLastSpaceByNBSP(text) -- Non Breakable SPace
local lastCharSpaceRegex = "^(.+) $"
local regex = "^(.+) (.+)$" --should pick last space(?)
text = text:gsub(lastCharSpaceRegex, "%1" .. nbspC):gsub(regex, "%1" .. nbspC .. "%2")
return text
end
function p.isCharacterKiller(character) return p.isKiller(character) end
function p.isKiller(character)
local character = p.resolveParameter(character)
return character.radius ~= nil
end
function p.getPossessiveName(character, params, fullName)
local langs = require("Module:Languages")
params = params or {link = p.bool(p.resolveParameter(character, "link", true)) or false}
params.fullName = fullName or p.bool(p.resolveParameter(character, "fullName", true)) or false
if not character.name then
character = p.getCharacterByName(p.resolveParameter(character))
end
if not character then return false end
params.firstName = p.getCharacterFirstName(character)
return langs.evaluatePossessive(character, params)
end
--[[
function p.getDativeName(character, params)
params = params or {link = p.bool(p.resolveParameter(character, "link", true)) or false}
if not character.name then
character = p.getCharacterByName(p.resolveParameter(character))
end
if _language ~= "en" then
return langs["dative_" .. _language](character, params) .. firstName
end
return firstName
end
]]
function p.clr(color, text)
text = text or p.resolveParameter(color, 2, true)
color = p.resolveParameter(color)
return clr(color, text)
--return frame:expandTemplate{title = "clr", args = {color, text}}
end
function p.biclr(color, text) return i(p.clr(color, text)) end
function p.ibclr(color, text) return i(p.clr(color, text)) end
--Colour Ordered
function p.clro(text, reset)
reset = reset or p.bool(p.resolveParameter(text, 2, true)) or false
text = p.resolveParameter(text, 1)
if not p.bool(frame:callParserFunction("#varexists:colorOrderCounter")) or reset then
frame:callParserFunction("#vardefine:colorOrderCounter", 1 )
end
local order = tonumber(frame:callParserFunction("#var:colorOrderCounter")) % (#_colorOrder + 1) --making list infinite by going back
order = (order == 0 and 1) or order --adjusting an index from order = #colorOrder => 0 + 1 = 1
frame:callParserFunction("#vardefine:colorOrderCounter", order + 1 )
return ((text ~= nil and text ~= cstr.empty) and b(clr(_colorOrder[order], text))) or clr(_colorOrder[order], text)
end
--Colour Ordered Reset
function p.clror(text)
return p.clro(text, true)
end
function p.clroTest()
local words = string.split(strings.clrTestString, space)
local result = cstr.empty
local clrOffset = 0
for i, word in ipairs(words) do
if #word <= 2 then clrOffset = clrOffset - 1 end
local clrIndex = i + clrOffset
local color = _colorOrder[clrIndex]
result = result .. (#word > 2 and p.tooltip(((i == 1 and p.clror(word)) or p.clro(word)), "order: " .. clrIndex .. br .. "colour: " .. color, true, true) or word) .. (clrIndex < #_colorOrder and space or cstr.empty)
if clrIndex == #_colorOrder then break end
end
return result .. ((#words > #_colorOrder and "...") or (#words < #_colorOrder and dot .. space .. brackets(strings.notEnoughColors)) or cstr.empty)
end
function p.bclr(color, text)
return b(p.clr(color, text))
end
function clr(color, text)
local result = 'inherit' --CSS value, don't change it
--mw.log(tonumber(color, 10))
color = tonumber(color, 10) or color:lower() --if the string is int then convert it to integer
if color == 1 or color == "brown" then result = "ab713c"
elseif color == "1bg" then result = "5d4533"
elseif color == 2 or color == "yellow" then result = "e8c252"
elseif color == "2bg" then result = "d7ad2f"
elseif color == 3 or color == "green" then result = "199b1e"
elseif color == "3bg" then result = "0f791f"
elseif color == 4 or color == "purple" then result = "ac3ee3"
elseif color == "4bg" then result = "672d7f"
elseif color == 5 or color == "pink" then result = "ff0955"
elseif color == "5bg" then result = "cf0b45"
elseif color == 6 or color == "orange" then result = "ff8800"
elseif color == "6bg" then result = "ff5300"
elseif color == 7 or color == "grey" then result = "808080"
elseif color == 8 or color == "red" then result = "d41c1c"
elseif color == 9 or color == "beige" then result = "e7cda2"
elseif color == 10 or color == "blue" then result = "0e98ff"
elseif color == 11 or color == "violet" then result = "b91a9b"
elseif color == "11bg" then result = "800080"
elseif color == 12 or color == "light blue" then result = "9bb0bf"
elseif color == "12bg" then result = "9bb0bf"
elseif color == 13 or color == "faded jade" then result = "418284"
elseif color == 14 or color == "gold" then result = "ffa800"
elseif color == "14bg" then result = "ffa800"
elseif color == 15 or color == "fuchsia" then result = "ec0dea"
elseif color == "15bg" then result = "b30ad2"
elseif color == 16 or color == "white" then result = "ffffff"
elseif color == 17 or color == "vomit green" then result = "8ad672"
elseif color == 18 or color == "blood red" then result = "900a0a"
elseif color == 19 or color == "bright red" then result = "ff0000"
elseif color == 20 or color == "silver" then result = "b5afb0"
elseif color == 21 or color == "turquoise" then result = "37d1c0"
elseif color == 22 or color == "cinnamon" then result = "b74004"
elseif color == 23 or color == "black" then result = "000000"
elseif color == 24 or color == "wiki gold" then result = "b7a269"
elseif color == 25 or color == "bronze" then result = "c2593a"
elseif color == 26 or color == "chartreuse" then result = "b6fa36"
elseif color == 27 or color == "screamin' green" then result = "63ef98"
elseif color == 28 or color == "lavender" then result = "b57edc"
elseif color == 29 or color == "aquamarine" then result = "7fffd4"
elseif color == 30 or color == "ultramarine" then result = "120a8f"
elseif color == 31 or color == "olive" then result = "808000"
elseif color == 32 or color == "pale rose" then result = "f5bfd9"
end
if text ~= nil and text ~= cstr.empty then
result = '<span class="luaClr clr clr' .. color .. '" style="color: ' .. ((result ~= 'inherit' and '#') or cstr.empty) .. result .. ';">' .. text .. '</span>'
end
return result
end
--calls for Strings function Syntax Highlight
function p.sh(value, lang, inline)
inline = p.bool(inline or p.resolveParameter(value, 3, true))
lang = lang or p.resolveParameter(value, 2, true)
value = p.resolveParameter(value)
return sh(value, lang, inline)
end
--text = text containing a tooltip
--tooltip = tooltip content
--iconless = true/false whether the (i) icon should appear after the text containing tooltip (default false)
--borderless = true/false whether underline should be shown (default false)
function p.tooltip(text, tooltip, iconless, borderless, pcViewOnly)
borderless = borderless or p.resolveParameter(text, 4, true) or p.resolveParameter(text, "borderless", true)
iconless = p.bool(iconless or p.resolveParameter(text, 3, true) or (p.resolveParameter(text, "tooltip") and p.resolveParameter(text, 2, true)) or p.resolveParameter(text, "iconless"))
tooltip = tooltip or p.resolveParameter(text, 2, true) or p.resolveParameter(text, "tooltip")
text = p.resolveParameter(text)
local containsLink = p.containsLink(tooltip)
return
'<span class = "tooltip' .. ((containsLink and space .. 'linkIncluded') or cstr.empty) .. ((borderless and space .. 'borderless') or cstr.empty) .. '">' ..
'<span class="tooltipBaseText' .. ((iconless and space .. 'iconless') or cstr.empty) .. '">' .. text .. '</span>'..
(((tooltip ~= nil and tooltip ~= cstr.empty) and
'<span class = "tooltiptext">' ..
((not pcViewOnly and '<span class = "mobileView"> (</span>') or cstr.empty) .. --if it's mobile view then apply add brackets...
'<span class="tooltipTextWrapper">' .. tooltip .. '</span>' ..
((not pcViewOnly and '<span class = "mobileView">) </span>') or cstr.empty) ..
'</span>') or cstr.empty) ..
'</span>'
end
function p.containsLink(text)
local regex = '%[%[.-%]%]'
local regexExternal = '%[.-%]'
local rawRegex = '?%&%#91;%&%#91;.-%&%#93;%&%#93;'
local rawRegexExternal = '%&%#91;.-%&%#93;'
if string.match(text, regex) or string.match(text, rawRegex) or
string.match(text, regexExternal) or string.match(text, rawRegexExternal)
then --we want to return boolean, not the index
return true
else
return false
end
end
function p.ptb(content, title, patch)
return
ptb(
p.resolveParameter(content),
title or p.resolveParameter(content, 2, true),
patch or p.resolveParameter(content, 3, true)
)
end
function ptb(content, title, patch)
title = title or p.resolveParameter(content, 2, true)
patch = patch or p.resolveParameter(content, 3, true)
if not patch or p.isPatchLive(patch) then
return content
else
return
dynamicBox(content, title, "ptb")
end
end
function p.deletedBox(content, title, patch)
return
deletedBox(
p.resolveParameter(content),
title or p.resolveParameter(content, 2, true),
patch or p.resolveParameter(content, 3, true)
)
end
function deletedBox(content, title, patch)
local result = dynamicBox(content, title, "deletedBox")
patch = (patch and string.trim(patch)) or nil
if patch and not p.isPatchLive(patch) then
--result = includeonly(result) .. noinclude(category("DeleteBox Remove Lists", "Remove List " .. patch))
result = result .. category("DeleteBox Remove Lists", "DeleteBox List for " .. patch)
end
return result --no further logic so redirecting straight to dynamic box
end
function p.wipBox(content, title, patch)
return
wipBox(
p.resolveParameter(content),
title or p.resolveParameter(content, 2, true),
patch or p.resolveParameter(content, 3, true)
)
end
function wipBox(content, title, patch)
local result = dynamicBox(content, title, "wipBox")
return result --no further logic so redirecting straight to dynamic box
end
function dynamicBox(content, title, boxType)
title = title or p.resolveParameter(content, 2, true)
boxType = boxType or p.resolveParameter(content, 3, true)
if not boxType then
return strings.boxTypeNotFound
end
return
'<div class = "dynamicBox ' .. boxType .. '">' ..
((title and '<div class = "dynamicTitle ' .. boxType .. '">' .. title .. '</div>' ) or cstr.empty) ..
'<div class = "dynamicContent ' .. boxType .. '">' .. content .. '</div>' ..
'</div>'
end
function p.isPatchLive(patchParam)
local patch = p.getPatch(patchParam)
--dLog(((patchParam and "[" .. patchParam .. ((not patch and " - Not Found") or cstr.empty) .. "]") or cstr.empty) .. " isPatchLive: " .. (patch and (p.IsFullDateTime(patch.rDate) and tostring(p.dateHasPassed(p.GetDatetime(patch.rDate)))) or tostring(false)))
return patch and (p.IsFullDateTime(patch.rDate) and p.dateHasPassed(p.GetDatetime(patch.rDate))) or false
end
function p.isCharacterLive(character)
if character.dlc then
local dlcM = require("Module:DLCs")
local dlc = dlcM.getCharacterMainDlc(character)
return (dlc == nil) or p.IsFullDateTime(dlc.rDate) and p.dateHasPassed(dlc.rDate)
end
return false
end
function p.getPatch(patch)
local data = require("Module:Datatable" .. p.lang())
for _, patchObj in ipairs(data.patches) do
if patchObj.patch == patch then return patchObj end
end
end
function p.getLatestPatch()
return require("Module:Datatable" .. p.lang()).latestPatch.patch
end
--timestamp = {year = 2022, month = 7, day = 19} --example format
function p.dateHasPassed(timestamp)
local currentDate = os.date("*t") --gives current timestamp: {year = 1998, month = 9, day = 16, yday = 259, wday = 4, hour = 23, min = 48, sec = 10, isdst = false}
local releaseOffset = 60 * 60 * 15 --release hour 15:00 (server time is -2 hours from my current time)
timestamp = p.standardiseDateObject(timestamp)
--mw.log(os.difftime(os.time(timestamp) + releaseOffset, os.time(currentDate)))
return os.difftime(os.time(timestamp) + releaseOffset, os.time(currentDate)) < 0 -- subtracting releaseOffset to adjust timing according to actual release hour
end
function p.isEventLive(element)
--not element.event is to skip events from different tables, such as tomes
return not (element.event or element.patch) and (element.rDate and p.dateHasPassed(element.rDate)) and (element.eDate == nil or not p.dateHasPassed(element.eDate))
or (element.patch and element.rDate and p.isFullDateTime(element.rDate) and p.dateHasPassed(element.rDate) and (element.eDate == nil or not p.dateHasPassed(element.eDate)))
end
function p.standardiseDateObject(dateObj)
if type(dateObj) == types.table then --if we pass table, we need to check time values and populate them to 0 by default
if not dateObj.hour then dateObj.hour = 0 end
if not dateObj.min then dateObj.min = 0 end
if not dateObj.sec then dateObj.sec = 0 end
dateObj = os.time(dateObj) --we convert it to seconds constant (resp. variable)
end
dateObj = p.GetDatetime(dateObj)
return dateObj
end
function p.resolvePatchList()
local data = require("Module:Datatable" .. p.lang())
local result = cstr.empty
local yearCounter = 0
local tempResult = cstr.empty
for _, patch in ipairs(data.patches) do
local patchDate = p.standardiseDateObject(patch.rDate)
local suffixLink = getSuffixLink(patch)
local suffixPatchName = getSuffixPatchName(patch) --in case of PTB we need to attach " (PTB)" suffix
if yearCounter ~= patchDate.year then
if yearCounter ~= 0 then
result = result .. frame:callParserFunction{ name = '#tag', args = {"tabber", tempResult} } .. nl
end
tempResult = cstr.empty
yearCounter = patchDate.year
result = result ..
"|-|" .. patchDate.year .. "=" .. nl
end
local patchNotes, result = pcall(function () return frame:expandTemplate{title = strings.patch .. space .. patch.patch .. suffixPatchName} end)
if patchNotes and --[[not patch.ptb and]] patch ~= patches[#patches] then
local patchVersions = string.split(patch.patch, '.')
if patchVersions[3] ~= "0" then patchVersions[3] = "0" end
for i = #patchVersions, 4 , -1 do --in case that patch contains anything else after last patch number let's remove it (example: "6.1.2 / 6.1.3")
table.remove(patchVersions)
end
local patchLink = strings.patch .. space .. patch.patch .. suffixPatchName
local patchLinkModified = strings.patch .. space .. table.concat(patchVersions, ".")
tempResult = tempResult ..
frame:expandTemplate{title = "!"}.. "-" .. frame:expandTemplate{title = "!"} .. patch.patch .. suffixPatchName .. "=" .. nl .. --creating individual patch tabs
frame:expandTemplate{title = "(!"} .. space .. 'class = "wikitable"' .. nl ..
"!" .. nl .. "==== " .. link(patchLinkModified .. ((suffixLink and suffixLink .. tl .. patchLink) or cstr.empty)) .. " - " .. p.toDate(os.time(patchDate)) .. " ====" .. nl .. --28 April 2022
frame:expandTemplate{title = "!-"} .. nl ..
frame:expandTemplate{title = "!"} .. result .. nl ..
frame:expandTemplate{title = "!)"} .. nl
end
end
result = result .. frame:callParserFunction{ name = "#tag", args = {"tabber", tempResult} } .. nl --post loop aiditon for last year
result = frame:extensionTag{name = 'tabber',content = result}
return result
end
function getSuffixPatchName(patch)
if patch.ptb then
return " (PTB)"
end
return cstr.empty
end
function getSuffixLink(patch)
local versions = string.split(patch.patch, '.')
if versions[3] ~= "0" then
return "#Hotfix " .. patch.patch
elseif patch.ptb then
return "#Public Test Build " .. patch.patch --patch needs to not contain "(PTB)" part
end
return nil
end
function p.getPatchLink(patch)
local suffixLink = getSuffixLink(patch)
local suffixPatchName = getSuffixPatchName(patch) --in case of PTB we need to attach " (PTB)" suffix
local patchLink = strings.patch .. space .. patch.patch .. suffixPatchName
local patchVersions = string.split(patch.patch, '.')
if patchVersions[3] ~= "0" then patchVersions[3] = "0" end
local patchLinkModified = strings.patch .. space .. table.concat(patchVersions, ".")
return link(patchLinkModified .. ((suffixLink and suffixLink .. tl .. patchLink) or cstr.empty))
end
--wt = wordTable
function p.getWord(wt, index)
local keys = table.keys(wt)
local maxValue = table.max(keys)
local minValue = table.min(keys)
if index > maxValue then return wt[maxValue] end --if the index is higher than highest amount, return word of highest amount available
if wt[index] then return wt[index] end -- if the index is exactly of plural count then return exact word on index
if index < minValue then return wt[minValue] end --if index not found, it means it's lower than lowest amount in a word table
for i, keyValue in ipairs(keys) do
if index < keyValue then
return wt[keys[i - 1]] --if the index is between two amounts(indexes) use the lower one
end
end
end
--{{header = 2022, content = "..."}, {}, ...}
function p.getTabberFromTable(tabberTable)
local result = cstr.empty
for _, section in ipairs(tabberTable) do
result = result .. nl .. '|-|' .. (section.header or strings.tabberHeaderNotFound) .. '=' .. nl .. section.content .. nl
end
result = frame:callParserFunction{ name = '#tag', args = {"tabber", result} }
return result
end
function p.isTagPresent(el, searchedTag)
if el.tags and type(el.tags) == types.table then
for _, tag in ipairs(el.tags) do
if string.lower(searchedTag) == string.lower(tag) then
return true
end
end
end
return false
end
function p.yesNo(param, note, imgSize)
imgSize = imgSize or p.resolveParameter(param, 3, true) or p.resolveParameter(param, "size", true)
note = note or p.resolveParameter(param, 2, true) or p.resolveParameter(param, "note", true)
param = p.resolveParameter(param, 1, true) or param
local imageFilename = "Unknown QuestionMark.png"
local tickClass = "unknown"
local size = (imgSize or 32) .. "px"
if p.bool(param) == true then
imageFilename = "DBD_UI_Icon_CheckMark.png"
tickClass = "tickYes"
elseif p.bool(param) == false then
imageFilename = "DBD_UI_Icon_X.png"
tickClass = "tickNo"
elseif param:lower() == "nan" then
imageFilename = "NaN.png"
tickClass = "tickNaN"
end
--lg(span(p.tooltip(span(file(imageFilename, size), tickClass),cstr.empty,true,true), "yesNo"))
return
span(
p.tooltip(
span(file(imageFilename, size), tickClass),
note or cstr.empty,
true,
true
)
, "yesNo")
end
--imgType:
-- perk = perk with switching background
-- sosPerk = teachable frame background
--icon icon that is inside
--imgSize currently not used, should be available for manually adjusting the size of image
function p.assembleImage(imgType, icon, imgSize, params)
local variousLogic = require("Module:Various")
params = params or p.resolveParameter(imgType, 4, true) --not sure if table can be passed directly from wiki
local rarity = p.resolveParameter(imgType, "rarity", true)
local isIconLink = p.resolveParameter(imgType, "iconLink", true) or p.resolveParameter(imgType, "il", true) or (params and params.iconLink == true) or false
local rarityNumber = tonumber(rarity) or (params and params.rarity and tonumber(params.rarity))
rarity = string.replace(((rarityNumber and (variousLogic.rarity[rarityNumber].techName or variousLogic.rarity[rarityNumber].name)) or (rarity or (params and params.rarity)) or variousLogic.rarity[1].techName or variousLogic.rarity[1].name):lower(), space, dash) --Common by default
rarity = string.replace(rarity, "&", "and")
imgSize = imgSize or p.resolveParameter(imgType, 3, true) or p.resolveParameter(imgType, "size", true)
if type(imgSize) == types.string then return "Error: " .. imgSize end
icon = icon or p.resolveParameter(imgType, 2, true) or p.resolveParameter(imgType, "icon", true)
imgType = p.resolveParameter(imgType, "type", true) or p.resolveParameter(imgType)
local imgSizeStyle = (imgSize and ' style = "width: ' .. imgSize .. 'px; height: ' .. imgSize .. 'px;"') or cstr.empty
local align = (params and params.align and "float: " .. params.align .. semi) or cstr.empty
if imgType == "sosPerk" then
return
'<div class = "game-element-container inline-flex relative">' ..
'<div class = "game-element-bg-settings game-element-bg-settings-size-sos teachable-perk-element"' .. imgSizeStyle .. '></div>' ..
'<div class = "game-element-bg-settings-size-sos game-element-imgObject absolute"' .. imgSizeStyle .. '>' .. file(p.getIcon(icon), "link=" .. icon) .. '</div>' ..
'<div class = "game-element-bg-settings game-element-bg-settings-size-sos teachable-indicator-perk-element absolute"' .. imgSizeStyle .. '></div>' ..
'</div>'
elseif imgType == "perk" then
return
'<div class = "game-element-container flex relative">' ..
'<div class = "game-element-bg-settings game-element-bg-settings-size-perk animate-perk-settings animate-perk-bg"' .. imgSizeStyle .. '></div> ' ..
'<div class = "game-element-bg-settings game-element-bg-settings-size-perk animate-perk-settings animate-perk-level absolute"' .. imgSizeStyle .. '></div>' ..
'<div class = "game-element-bg-settings-size-sos game-element-imgObject absolute"' .. imgSizeStyle .. '>' .. file(p.getIcon(icon), "link=" .. icon, "center") .. '</div>' ..
'</div>'
elseif table.contains({"addon", "item", "power", "offering"}, imgType) then
--rarity = (rarity and variousLogic.rarity[rarity].name:lower()) or string.replace((params and (params.rarity and (tonumber(params.rarity) and variousLogic.rarity[params.rarity].name) or params.rarity) or variousLogic.rarity[1].name):lower(), space, dash)
addonContainer = (imgType == "addon" and "addon-container") or cstr.empty
imgSize = imgSize or 128
if isIconLink then
local ilOffset = (imgSize / 2)
local addonMarkerScale = 50 * (imgSize / 128)
return
'<span style="display:inline-flex; position:relative; height: ' .. ilOffset .. 'px; width: ' .. imgSize .. 'px">' ..
'<span class = "margin-auto" style = "display: inline-flex; position: absolute; top: -6px; max-width: ' .. imgSize .. 'px;' .. align ..'">' ..
'<span class = "game-element-container ' .. addonContainer .. ' flex relative">' ..
'<span class = "game-element-bg-settings game-element-bg-settings-size-perk ' .. rarity .. '-' .. (table.contains({"item", "offering"}, imgType) and imgType or "item") .. '-element" ' .. imgSizeStyle .. '></span>' ..
((imgType == "addon" and '<span class = "game-element-bg-settings game-element-bg-settings-size-perk addon-marker addon-marker-il absolute" style = "scale: ' .. addonMarkerScale .. '%;"></span>') or cstr.empty) ..
'<span class = "game-element-bg-settings-size-sos absolute" ' .. imgSizeStyle .. '>' .. file(p.getIcon(icon), "link=" .. icon, "300px") .. '</span>' ..
'</span>' ..
'</span>' ..
'</span>'
else
return
'<div class = "margin-auto" style = "max-width: ' .. imgSize .. 'px; scale: calc(' .. imgSize .. '/128); ' .. align ..'">' ..
'<div class = "game-element-container ' .. addonContainer .. ' flex relative">' ..
'<div class = "game-element-bg-settings game-element-bg-settings-size-perk ' .. rarity .. '-' .. (table.contains({"item", "offering"}, imgType) and imgType or "item") .. '-element"></div>' ..
((imgType == "addon" and '<div class = "game-element-bg-settings game-element-bg-settings-size-perk addon-marker absolute"></div>') or cstr.empty) ..
'<div class = "game-element-bg-settings-size-sos absolute">' .. file(p.getIcon(icon), "link=" .. icon, "300px") .. '</div>' ..
'</div>' ..
'</div>'
end
end
end
function p.resolvePageHeader(params)
return nil
end
return p