local p = {}
local mathOps = require("Módulo:MathOps")
local str = require("Módulo:Strings")
local frame = mw.getCurrentFrame()
local _brigtnessTreshold = 0.36
local _isValidFileNameCounter = 0
local strings = {
male = "Masculino",
female = "Femenino",
nonhuman = "No Aplicable (no humano)";
undefined = "Indefinido",
charNotFound = "Character not found!",
patch = "Parche"
}
local months = {
January = "Enero",
February = "Febrero",
March = "Marzo",
April = "Abril",
May = "Mayo",
June = "Junio",
July = "Julio",
August = "Agosto",
September = "Septiembre",
October = "Octubre",
November = "Noviembre",
December = "Diciembre"
}
local days = {
Monday = "Lunes",
Tuesday = "Martes",
Wednesday = "Miercoles",
Thursday = "Jueves",
Friday = "Viernes",
Saturday = "Sábado",
Sunday = "Domingo"
}
p.strings = strings
----------------------------------------------
p.timeFormat1 = "%d.%m.%Y" -- 5.9.2022
timeContants = {
minute = 60, --in seconds
hour = 3600,
day = 86400,
week = 604800
}
--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 params) or (param.args and param.args[(index or 1)] and p.replaceSpecialCharacters(param.args[(index or 1)]))) 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) end --if parameter was passed directly or not at all
if retArg == nil and type(param) == "string" and not returnCanBeNil then retArg = param end --simply pass string parameter, and proccess it (with special char removal).
if retArg ~= nil and type(param) == "string" then retArg = p.replaceSpecialCharacters(retArg) end --final processing
return retArg
end
function getParamOrPageName(param)
if param and
(type(param) ~= types.table or
(type(param) == types.table and not param.args))
then
return param
else
local pageName = mw.title.getCurrentTitle().text
return p.split(pageName, '/')[1] -- remove page suffixes such as language codes
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 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
--finding any strings need to be first as span regex WILL 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
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, "#span" .. spanCounter)
spanCounter = spanCounter + 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("#span(%d)") do --now we put spans back into string
local currentRegexString = "#span" .. 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 list = maps
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 getCountDlcType(1)
elseif subject == "paragraph" then return getCountDlcType(2)
elseif subject == "clothing" then return getCountDlcType(3)
elseif subject == "ost" then return getCountDlcType(4)
elseif subject == "character" then return 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 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("Módulo:Datatable/Perks").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)
require("Módulo:Datatable")
for _, currency in ipairs(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
function p.split(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t = {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
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 error"Unable to convert to number" end
if s == math.huge then error"Unable to convert infinity" end
s = math.floor(s)
if s <= 0 then return s end
local ret = ""
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"
--Cowshed
--mw.log(romanNumber)
result = str
--result = string.sub(str, 1, index - 1) .. ToNumeral(romanNumber)
else
--mw.log("Non-Roman form")
result = str
end
return result
end
function p.CapitalizeName(str)
--mw.log(string.gsub(" "..str, "[%s%-]%a", string.upper):sub(2))
return string.gsub(" "..str, "[%s%-]%a", string.upper):sub(2)
end
function p.FirstLetterLower(str)
return str:sub(1, 1):lower() .. str:sub(2)
end
function p.FirstLetterUpper(str)
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, "%&", "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 = str:gsub(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(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)
_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
return name .. dot .. cstr.png
end
function p.GetDisplayName(item)
return (item.tName or item.name)
end
function p.IconLink(icon, pageLink, displayText)
local result = cstr.empty
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 = p.getIconObject(icon)
local iconFile = iconObject.iconFile
local filename = cstr.file .. iconFile .. tl .. "link=" .. (pageLink or icon)
local text = (displayText 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
elseif pageLink == "Teachable" then
local prkz = require("Módulo:Perks")
text = ((displayText and displayText .. tl .. icon) or icon) .. space
filename = cstr.file .. prkz.getTeachablePerkIconFilename({args={icon, 'png'}})
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 = link(filename .. tl .. "32px")
--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 style = "padding: ' .. padding.top .. space .. padding.right .. space .. padding.bottom .. space .. padding.left .. '">' ..
'<span style = "display:none" class = "pcView">' .. p.tooltip(imageFile, link(filename, "96px", "link="), true, true) .. '</span>' ..
'<span class = "mobileView">' .. imageFile .. '</span>' ..
'</span>'
mw.log(result)
return result
--return frame:expandTemplate{title = "IconLink", args = {icon, pageLink, displayText} }
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
--local prkz = require("Módulo:Perks")
--local perk = prkz.getPerkByName(icon)
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.getSumOfASTiles(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 compASTiles(row1, row2)
local sum1 = row1.ASTiles
local sum2 = row2.ASTiles
if(type(sum1) == "table") then
sum1 = p.getSumOfASTiles(sum1) --converting back from table to number
end
if(type(sum2) == "table") then
sum2 = p.getSumOfASTiles(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 compMapRateSize(row1, row2)
return row1.size > row2.size --comparison specificly made for mapRates table
end
function compName(row1, row2)
if row1.diacritics then
if row2.diacritics then
return p.RemoveSpecialCharacters(row1.name, true) < p.RemoveSpecialCharacters(row2.name, true)
else
return p.RemoveSpecialCharacters(row1.name, true) < row2.name
end
elseif row2.diacritics then
return row1.name < p.RemoveSpecialCharacters(row2.name, true)
end
return row1.name < row2.name
end
function compInt(row1, row2) -- 1, 2, 3
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.id < row2.id
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 < row2.rarity then
return true
elseif 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.sortMapsByASTiles()
local m = require("Módulo:Datatable")
--mw.log(mw.dumpObject(maps))
table.sort(maps,compASTiles)
--mw.log(mw.dumpObject(maps))
end
function p.sortMapRates(rankTable)
--mw.log(mw.dumpObject(rankTable))
table.sort(rankTable, compMapRateSize)
--mw.log(mw.dumpObject(rankTable))
end
function p.sortItemsByName(tableList)
table.sort(tableList, compName)
end
function p.sortTable(tableList)
table.sort(tableList, compInt)
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
---------------------------------------------------------------------------------
function p.getCountOfSoundtracks()
return utils.getCount("ost")
end
function p.today()
return os.time(os.date("!*t"))
end
function p.getMonth(stamp)
if type(stamp) ~= 'number' then
stamp = p.toTimestamp(stamp)
end
return os.date("%m", stamp)
end
function p.getDay(stamp)
if type(stamp) ~= 'number' then
stamp = p.toTimestamp(stamp)
end
return os.date("%d", stamp)
end
function p.getTimeDiff(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)
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)
local day, month, year = datestamp:match("^(..)%.(..)%.(....)$")
if month == "##" or day == "##" or year == "####" then return false end
return true
end
function p.GetDatetime(datestamp)
local result = {}
if type(datestamp) == types.string then
local day, month, year = datestamp:match("^(..)%.(..)%.(%d%d%d%d)$")
if month == "##" then
month = 1
result.fakeMonth = true
end
if day == "##" then
day = 1
result.fakeDay = true
end
result.year = year
result.month = month
result.day = day
result.timestamp = p.toTimestamp(result)
local tempDateObj = os.date("*t", os.time(result))
result.yday = tempDateObj.yday
result.wday = tempDateObj.wday
result.dst = tempDateObj.isdst
elseif type(datestamp) == types.number then
result = os.date("*t", datestamp)
result.timestamp = datestamp
end
return result
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 = ""
local regexString = regex --"#pl%((%d)%)" -- looking and extracting number from "#pl(x)"
for key, value in pairs(regexTable) do
reggedString = reggedString:gsub(key, value)
end
--for m in reggedString:gmatch(regex) do
-- mw.log(mw.dumpObject(m))
-- result = reggedString:gsub(regexString, "string")
-- mw.log(result)
--end
return reggedString
end
function p.getIcon(icon, searchParam)
return p.getIconObject(icon, searchParam).iconFile
end
function p.getIconObject(icon, searchParam)
searchParam = searchParam or p.resolveParameter(searchParam, 2, true)
icon = p.resolveParameter(icon)
local icons = mw.loadData("Módulo:Datatable/Icons").icons
for _, element in ipairs(icons) do
if (searchParam and element[searchParam] == icon) or element.techName == icon or icon == element.icon then
return element
end
end
return icons[1] --Icon not found, then pick the first one which is supposed to be the Unknown one
end
--************************* survivors & killers ******************************--
function p.getCharacterById(id, charTable)
for _, character in ipairs(charTable) do
if character.id == id then return character end
end
return strings.charNotFound
end
function p.getCharacter(object)
local data = require("Módulo:Datatable")
if object.killer or object.charType == 'K' then
return p.getCharacterById(object.killer or object.id, killers)
else
return p.getCharacterById(object.survivor or object.id, survivors)
end
end
function p.resolveCharacterPortraitFileName(character, maxId)
local fileConst = "_charPreview_portrait"
local isKiller = character.power
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 = ((character.power and 'K') or 'S') .. string.format("%02d", character.id) .. 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.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
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
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)
local str = require("Módulo:Datatable")
for _, surv in ipairs(survivors) do
if surv.shortName == name or surv.name == name then
return surv
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
return killer
end
end
return nil
end
function p.getCharsByDlc(dlc, charType)
local str = require("Módulo:Datatable")
local result = {}
local listTable = (charType == 'S' and survivors) or (charType == nil and survivors) or 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(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.power ~= nil
end
function p.getPossessiveName(character, params)
params = params or {link = p.bool(p.resolveParameter(character, "link", true)) or false}
local langs = require("Módulo:Languages")
if not character.name then
character = p.getCharacterByName(p.resolveParameter(character))
end
local firstName = p.getCharacterFirstName(character)
if _language ~= "en" then
return langs["possessive_" .. _language](character, params) .. firstName
end
if character.possessive == true then
firstName = firstName .. "'" --if it's true then just add the apostrof to the end
elseif character.possessive then --it's not true nor false nor empty, hence it should be string (not checked)
firstName = character.possessive
else
firstName = firstName .. "'s" --if nothing then simply add 's after first name
end
return firstName
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 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 = "26eaea"
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"
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
--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)
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) .. ((iconless and space .. 'iconless') or cstr.empty) .. ((borderless and space .. 'borderless') or cstr.empty) .. '">' ..
'<span class="tooltipBaseText">' .. text .. '</span>'..
'<span class = "tooltiptext">' ..
'<span class = "mobileView"> (</span>' .. --if it's mobile view then apply add brackets...
'<span class="tooltipTextWrapper">' .. tooltip .. '</span>' ..
'<span class = "mobileView">) </span>' ..
'</span>' ..
'</span>'
end
function p.containsLink(text)
local regex = '%[%[.-%]%]'
local rawRegex = '?%&%#91;%&%#91;.-%&%#93;%&%#93;'
if string.match(text, regex) or string.match(text, rawRegex) 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 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("Módulo:DLCs")
local dlc = dlcM.getDlcById(character.dlc).rDate
return p.IsFullDateTime(dlc) and p.dateHasPassed(dlc)
end
return false
end
function p.getPatch(patch)
local data = require("Módulo:Datatable")
for _, patchObj in ipairs(patches) do
if patchObj.patch == patch then return patchObj end
end
end
function p.getLatestPatch()
return require("Módulo:Datatable").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.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)
--deprecated, GetDatetime function now accpets seconds variable
--if type(dateObj) == types.string then --handling other formats, such as "19.07.2022"
-- dateObj = p.GetDatetime(dateObj) -- os.date("*t", os.time())
--elseif type(dateObj) == types.number then --we convert constant back to table that is going to have all time/date fields (or if the constant is passed directly to the function)
-- dateObj = p.GetDatetime(dateObj) --p.GetDatetime(os.date("*t", dateObj))
--end
return dateObj
end
function p.resolvePatchList()
local data = require("Módulo:Datatable")
local result = cstr.empty
local yearCounter = 0
local tempResult = cstr.empty
for _, patch in ipairs(patches) do
local patchDate = p.standardiseDateObject(patch.rDate)
dmp(patchDate)
local hotfix = resolveHotfix(patch)
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} end)
if patchNotes and --[[not patch.ptb and]] patch ~= patches[#patches] then
local patchVersions = p.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
local patchLinkModified = strings.patch .. space .. table.concat(patchVersions, ".")
tempResult = tempResult ..
frame:expandTemplate{title = "!"}.. "-" .. frame:expandTemplate{title = "!"} .. patch.patch .. "=" .. nl .. --creating individual patch tabs
frame:expandTemplate{title = "(!"} .. space .. 'class = "wikitable"' .. nl ..
"!" .. nl .. "==== " .. link(patchLinkModified .. ((hotfix and hotfix .. 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}
mw.log(result)
return result
end
function resolveHotfix(patch)
local versions = p.split(patch.patch, '.')
if versions[3] ~= "0" then
return "#Hotfix " .. patch.patch
end
return nil
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 .. '=' .. nl .. section.content .. nl
end
result = frame:callParserFunction{ name = '#tag', args = {"tabber", result} }
return result
end
return p
Advertisement
Módulo:Utils
Advertisement