Dead by Daylight Wiki
Advertisement

local p = {}
local mathOps = require("Module:MathOps")
local str = require("Module:Strings")
local frame = mw.getCurrentFrame()
local _brigtnessTreshold = 0.36
local _isValidFileNameCounter = 0

local strings = {
	male = "Male",
	female = "Female",
	nonhuman = "Not applicable (not human)";
	undefined = "Undefined",
	charNotFound = "Character not found!",
}

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.strings = strings
--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 = mw.title.getCurrentTitle().text 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 = (param or mw.title.getCurrentTitle().text) 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 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 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("Module: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("Module: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.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 = { 
		["'"] = '&#39;',
		["&"] = '&#38;'
	}
	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.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 file = cstr.file .. p.getIcon(icon) .. 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
		file = cstr.file .. p.getIcon(icon) .. tl .. "link=" .. icon
	elseif pageLink == "Teachable" then
		local prkz = require("Module:Perks")
		text = ((displayText and displayText .. tl .. icon) or icon) .. space
		file = 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 pageLink == "linkless" then
		text = (displayText or icon)
	end	
	
	file = link(file .. tl .. "32px")
	
	--result = '<span class = "wrap-span pcView" style = "display:none;"><span class="box-span">' .. boxDesc .. '</span>' .. text .. file .. '</span>'
	
	result = text .. file
	
	--mw.log(result)
	return result
	--return frame:expandTemplate{title = "IconLink", args = {icon, pageLink, displayText} }	
end

function getBoxDescription(icon) --curently disabled
	local ic = p.getIconObject(icon)
	local result = cstr.empty
	if ic.category == "Perks" then
		--local prkz = require("Module: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)
	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 p.sortMapsByASTiles()
	local m = require("Module: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.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.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.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 day, month, year = datestamp:match("^(..)%.(..)%.(%d%d%d%d)$")
	if month == "##" then month = 1 end
	if day == "##" then day = 1 end
	return {year = year, month = month, day = day}
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)
	return p.getIconObject(icon).iconFile
end

function p.getIconObject(icon)
	icon = p.resolveParameter(icon)
	local icons = mw.loadData("Module:Datatable/Icons").icons
	
	for _, element in ipairs(icons) do
		if 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.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 and not p.isValidFileName(fileName)) or (not maxId and not p.isValidFileName(fileName))) then --File not Found
		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)
	return '{| class = ' .. quotes('wikitable' .. ((classes and space .. p.getTableClasses(classes)) or cstr.empty)) .. ((styles and space .. 'style = ' .. quotes(styles)) or cstr.empty) .. nl .. ntl .. nl .. 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("Module:Datatable")
	for _, surv in ipairs(survivors) do
		if 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("Module: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)
	local character = p.resolveParameter(character)
	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
	regex = "(.+) (.+)" --should pick last space(?)
	text = text:gsub(regex, "%1" .. nbsp .. "%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)
	local langs = require("Module: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) .. 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.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

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

return p
Advertisement