Module:Various

From Dead by Daylight Wiki
Jump to navigation Jump to search

local p = {}
local str = require("Module:Strings")
local utils = require("Module:Utils")
local _bothThemesStartingDlcId = 37 --Resident Evil™: PROJECT W

p.strings = {
	characterNotFound = ibclr(6, "Character wasn't found." .. cstr.contact),
	mapNotFound = ibclr(6, "Map wasn't found." .. cstr.contact),
	noMapAchiev = ibclr(6, "This map doesn't have any achievement."),
	noDesc = i("Description wasn't found"),
	missingName = i("Missing name"),
	missingCharType = 'Missing parameter ' .. bclr("orange", charType) .. '. Allowed values are "K" or "S" (without quotes).',
	
	ptbHeader = "This description is based on the changes featured in the upcoming Patch: #patch#",
	wipHeader = "This description is being worked on or will be changed soon in order to improve clarity.",

	--events
	anniversaryEvent     = "Anniversary Event",
	cosmeticRewards      = "Cosmetic Rewards Event",
	crossoverEvent       = "Crossover Event",
	eventTome            = "Event Tome", --also used for as Event Tome link on main page
	modifierTome         = "Modifier Tome",
	halloweenEvent       = "Halloween Event",
	lunarEvent           = "Lunar Event",
	miniEvent            = "Mini-Event",
	springEvent          = "Spring Event",
	summerEvent          = "Summer Event",
	winterEvent          = "Winter Event",
	
	communityChallenge     = "Community Challenge",
	communityChoiceEvent   = "Community Choice Event",
	doubleDR               = "Double Daily Ritual Bloodpoints Event",
	doubleRF               = "Double Rift Fragments Event",
	doubleXP               = "Double XP Event",
	matchXP                = "Match XP Event",
	fMOTDXP                = "First Match of the Day Bonus XP Event",
	tomeCommunityChallenge = "Tome Community Challenge",
	
	bloodRush              = "Blood Rush",
	bloodHunt              = "Blood Hunt",
	bloodFeast             = "Blood Feast",
	bloodFury              = "Blood Fury", --not used currently
	
	fragmentFrenzy         = "Fragment Frenzy",
	fragmentFeast          = "Fragment Feast",
	fragmentFury           = "Fragment Fury",
	
	doublePlayerXP         = "Double Player XP Event",
	triplePlayerXP         = "Triple Player XP Event",
	
	newDlcReleased = "New DLC Released",
	
	unknownDesc = bclr(16, "THIS EFFECTS OF THIS #element ARE UNKNOWN."),	-- currently not used
	retired = bclr(13, "THIS #element HAS BEEN RETIRED."),
	mobile = bclr(6, "THIS #element IS ONLY AVAILABLE ON MOBILE."),			-- currently not used
	deprecated = bclr(5, "NO LONGER AVAILABLE IN THE BLOODWEB."),			-- currently not used
	notAvailable = bclr(8, "NO LONGER AVAILABLE."),							-- currently not used
	unused = bclr(8, "THIS #element IS UNUSED."),							-- currently not used
	decom = bclr(8, "THIS #element HAS BEEN DECOMMISSIONED."),				-- currently not used
	hidden = ibclr(6, "THIS #element IS HIDDEN"),

	day = {
		[1] = "day",
		[2] = "days",
	},
	--dayOrder = #1#
	
	killer = "Killer",
	killers = "Killers",
	surv = "Survivor",
	survs = "Survivors",
	
	icon = "Icon",
	name = "Name",
	desc = "Description",
	adept = "Adept",
	
	currentEvents = "Current Event(s)", --main page heading
	event = "Event",
	modifier = "Modifier",
	gameMode = "Game Mode",
	tome = "Tome", --used for tome page name, ex. "Tome 16 -- Existence"
	new = "New",
	patch = "Patch",
}
local strings = p.strings
if utils.lang() ~= cstr.empty and (counter or 0) < 1 then
	counter = (counter or 0) + 1
	strings = require("Module:Various" .. utils.lang()).strings
end

--------------
-- RARITIES --
--------------
p.rarity = {
	[0]  = {id =  0, name = "Unused"},
	[1]  = {id =  1, name = "Common"},
	[2]  = {id =  2, name = "Uncommon"},
	[3]  = {id =  3, name = "Rare"},
	[4]  = {id =  4, name = "Very Rare"},
	[5]  = {id =  5, name = "Ultra Rare"},
	[6]  = {id =  6, name = "Legendary",                                                                    clr = 12},
	[7]  = {id =  7, name = "Teachable",                                                                    clr = 6},
	[8]  = {id =  8, name = "Event",                                                                        clr = 14},
	[9]  = {id =  9, name = "Artifact",                                                                     clr = 15},
	[10] = {id = 10, name = "Spiritual"},
	[11] = {id = 11, name = "Limited",                                                                      clr = 25},
	[12] = {id = 12, name = "Craft-able & " .. utils.clr(25, "Limited"), techName = "Craft-able & Limited", clr = false},
	[20] = {id = 20, name = "Retired",                                                                      clr = 13},
	[21] = {id = 21, name = "Decommissioned",                                                               clr = false},
}

p.elementStrings = {
	items = "ITEM",					--currently not used
	offerings = "OFFERING",			--currently not used
	addons = "ADD-ON",				--currently not used
	perks = "PERK",					--currently not used
	achievements = "ACHIEVEMENT"	
}
----------------------------------------------
------------
-- PRICES --
------------
p.elementCost = {
	[1] = 2000, -- Common
	[2] = 2500, -- Uncommon
	[3] = 3250, -- Rare
	[4] = 4000, -- Very Rare
	[5] = 5000, -- Ultra Rare
	[8] = 2000, -- Event
}

local elementType = cstr.empty
local elementTable = {}
local elementDescTable = {}

local function elementUpperString() return string.upper(elementType) end
local function elementCapitalised() return utils.capitalizeName(elementType) end

--Table used for mapping which string should use which style
p.elementStringMapper = {
	elementNotFound = elementCapitalised,
	elementDescNotFound = elementCapitalised,
	
	unknownDesc = elementUpperString,
	retired = elementUpperString,
	mobile = elementUpperString,
	deprecated = elementUpperString,
	unused = elementUpperString,
	decom = elementUpperString,
	hidden = elementUpperString,
}
function setElementTable(elType)
	local elementTypeList = {
		addons = "Loadout",
		items  = "Loadout",
		offerings = "Offerings",
		achievements = "Achievements",
		perks = "Perks"
	}
	local elementData = mw.loadData("Module:Datatable/" .. utils.capitalizeName(elementTypeList[elType]) .. utils.lang())
	elementType = p.elementStrings[elType]
	elementTable = elementData[elType]
	elementDescTable = elementData[string.replace(elementType:lower(), '-', cstr.empty) .. "Descriptions"]
end

--this list is to indicate which event types should be recognised for auto-flagging loadout (being deprecated)
p.eventTypes = {
	anniversaryEvent = "anniversaryEvent",
	halloweenEvent	 = "halloweenEvent",
	lunarEvent		 = "lunarEvent",
	summerEvent		 = "summerEvent",
	winterEvent		 = "winterEvent"
}
--Following strings are searched as a part of various string
--example: Anniversary Blood Rush => contains the string "Blood Rush" - this part gets redirected to appropriate page
local _eventList = {
	strings.bloodRush,
	strings.bloodHunt,
	strings.bloodFeast,
	strings.communityChoiceEvent
}
--List to be excluded as a link in Past Events section
local _excludedEventList = {
	strings.doubleDR,
	strings.doubleRF,
	--strings.doublePlayerXP,
	strings.doubleXP,
	strings.matchXP,
	strings.fMOTDXP,
}

--------------------------------------------------------------------------------
function wrapPtb(element)
	if element.desc ~= nil then
		local processedString = processString(strings.ptbHeader, element)
		element.desc = ptb(element.desc, processedString, element.patch)
	end
	return element
end

function wrapWipBox(element)
	if element.desc ~= nil then
		local wipHeader = processString(strings.wipHeader, element)
		element.desc = utils.wipBox(element.desc, wipHeader, element.patch)
	end
	return element
end

function processString(ptbString, element)
	local data = require("Module:Datatable" .. utils.lang())
	local patchSub = "#patch#"
	local latestPatchSub = "#lpatch#"
	
	local ptbString = ptbString:gsub(patchSub, (element and element.patch) or cstr.empty):gsub(latestPatchSub, data.latestPatch.patch)
	
	return ptbString
end
--------------------------------------------------------------------------------

function p.getElementsByCategory(cat, elTable, charType)
	local result = {}
	cat = cat and string.split(cat, ',')
	
	if cat[#cat] == "all" then
		if charType then
			return getAllCharTypeElements(charType, elTable)
		end
		for elName, el in pairs(elTable) do
			local element = table.copy(el)
			element.name = elName
			table.add(result, element)
		end
		table.addRange(result, getAllCharTypeElements(charType, elTable))
		return result
	elseif cat[#cat] == "general" then
		return getGeneralElementsByCharType(charType, elTable)
	end
	for elName, el in pairs(elTable or (elementType ~= cstr.empty and elementTable)) do
		if (charType == nil or el.charType == charType or ((charType == 'S' and el.itemsCategory) or (charType == 'K' and el.killer))) then
			local found = false
			for _, searchedCat in ipairs(cat) do
				if	(searchedCat == "retired" and el.retired) or
					(searchedCat == "hidden" and el.hidden) or
					(searchedCat == "killer" and el.killer) or
					(searchedCat == "unused" and (el.unused or el.unusedVisible)) or
					(searchedCat == "decom" and el.decom) or
					(searchedCat == "mobile" and el.mobile) or
					(searchedCat == "deprecated" and el.deprecated) then
					
					local element = table.copy(el)
					element.name = element.name or elName
					table.add(result, element)
					break
				else
					if el.tags ~= nil then
						for _, category in ipairs(el.tags) do
							if category == searchedCat then
								local element = table.copy(el)
								element.name = element.name or elName
								table.add(result, element)
								found = true
								break
							end
						end
					end
				end
				if found then break end
			end
		end
	end
	return result
end

function p.getCountElementsByCategory(cat, elTable, charType)
	local category = utils.resolveParameter(cat)
	local charType = charType or utils.resolveParameter(cat, 2, true)
	
	return #p.getElementsByCategory(category, elTable, charType)
end

function p.getElementsByNames(names, elTable)
	local result = {}
	elTable = elTable or (elementType ~= cstr.empty and elementTable)
	names = names and string.split(names, ',')
	
	for _, elName in ipairs(names) do
		if elTable[elName] then
			local element = table.copy(elTable[elName])
			element.name = elName
			table.add(result, element)
		end
	end

	return result
end

function p.getElementsByRarities(searchedRarities, elTable)
	local result = {}
	local rarityObjs = {}
	searchedRarities = type(searchedRarities) == types.string and string.split(searchedRarities, ',') or tonumber(searchedRarities) and {searchedRarities} or searchedRarities
	
	for _, searchedRarity in ipairs(searchedRarities) do
		for i, rar in pairs(p.rarity) do
			if tonumber(searchedRarity) == i or searchedRarity == rar.name then --type(searchedRarity) == types.string and
				rar.rarity = i
				table.add(rarityObjs, rar)
				break
			end
		end
	end

	for elName, el in pairs(elTable or (elementType ~= cstr.empty and elementTable)) do
		if table.contains(table.get(rarityObjs, "rarity"), el.rarity) then
			local element = table.copy(el)
			element.name = elName
			table.add(result, element)
		end
	end

	return result
end

function p.getElementCost(el, raw)
	return (raw and p.elementCost[el.rarity]) or
		((p.elementCost[el.rarity] and utils.formatNum(p.elementCost[el.rarity], 0)) or " -") .. '<p style="margin: 4px">' .. file(utils.getIcon(ils.bpWhite), "56px") .. '</p>'
end

function getAllCharTypeElements(charType, elTable)
	local result = {}
	for elName, el in pairs(elTable) do
		if (charType == nil or el.charType == charType) and not el.unused then
			local element = table.copy(el)
			element.name = element.name or elName
			table.add(result, element)
		end
	end
	return result
end

function getGeneralElementsByCharType(charType, elTable)
	local result = {}
	for elName, el in pairs(elTable) do
		if not el.character and (el.charType == charType or charType == nil) and not el.unused then
			local element = table.copy(el)
			element.name = element.name or elName
			result[elName] = element
			table.add(result, element)
		end
	end
	return result
end

function p.fillElementInfo(element)
	if type(element.map) == types.number then
		local mapsLogic = require("Module:Maps" .. utils.lang())
		element.map = mapsLogic.getMapById(element.map)
	end
	if type(element.realm) == types.number then
		local mapsLogic = require("Module:Maps" .. utils.lang())
		element.realm = mapsLogic.getRealmById(element.realm)
	end
	if type(element.killer) == types.number then
		local killersLogic = require("Module:Killers" .. utils.lang())
		element.killer = killersLogic.getKillerById(element.killer)
	end
	if type(element.survivor) == types.number or type(element.surv) == types.number then
		local survivorsLogic = require("Module:Survivors" .. utils.lang())
		element.killer = survivorsLogic.getSurvivorById(element.survivor or element.surv)
	end
end
--cat = category --this cannot be tested as usual, in order to pass the parameter in debug it needs to be passed like this: {args = {"category"}}
function p.getPastEventsTabber(cat)
	cat = utils.resolveParameter(cat, 1, true)
	local data = mw.loadData("Module:Datatable" .. utils.lang())
	local tabberTable = {}
	local catData = (cat and p.getElementsByCategory(cat, data.events)) or nil

	local year
	local tabberRow = {content = cstr.empty}
	for _, event in ipairs(catData or data.events) do
		local rDateFull = utils.IsFullDateTime(event.rDate)
		local eDateFull = utils.IsFullDateTime(event.eDate)
		local rDate = utils.standardiseDateObject(event.rDate)
		local eDate = utils.standardiseDateObject(event.eDate)
		local eventDateInfo = {rDateFull = rDateFull, eDateFull = eDateFull, rDate = rDate, eDate = eDate}
		
		if not year then --first run
			year = rDate.year
		end
		if year ~= rDate.year then
			table.insert(tabberTable, tabberRow)
			tabberRow = {content = cstr.empty}
			year = rDate.year
		end
			
		tabberRow.header = year
		local eventType = getEventType(event) --The Event type is used as a title for tooltip, mapped via tables at the top. If eventType is not evaluated and event.note is not present the Title is not mapped and tooltip won't appear
		local eventIcon = file(utils.getIcon(event.icon), '64px', 'link=' .. getEventIconLink(event))
		local eventDurationString = getEventDurationString(event, eventDateInfo)
		local rowText = getEventNameWithLink(event)
		tabberRow.content = tabberRow.content ..
			nbullet .. space .. eventIcon .. space ..
				(((eventType or event.note) and --if this condition is false then the Tooltip won't be present #EDIT: it will be always true now, as the eventType will always have a value (event name is now default value)
					utils.tooltip(
						rowText,
						((eventType and b(eventType) .. br) or cstr.empty) .. 
						((event.img and br .. file(event.img, '250px', 'link=') ..br) or cstr.empty) ..
						((event.note and small(event.note) .. br) or cstr.empty) ..
						eventDurationString,
						true, true)
				) or rowText).. nl
				
		--if rDateFull and utils.dateHasPassed(rDate) then
		--else
		--end
	end
	
	table.insert(tabberTable, tabberRow)
	

	--lg(utils.getTabberFromTable(tabberTable))
	return utils.getTabberFromTable(tabberTable)
end

function getEventIconLink(event)
	for _, excludedEvent in ipairs(_excludedEventList) do 
		if string.find(event.name, excludedEvent) or event.name == excludedEvent then
			return cstr.empty
		end
	end
	for _, eventItem in ipairs(_eventList) do
		if string.find(event.name, eventItem) then
			return eventItem
		end
	end

	return event.name
	
	--if utils.pageExists(event.name) then --we should find better way thanrather than checking page existence
	--	table.insert(_eventList, event.name)
	--end
end

function getEventDurationString(event, eventDateInfo)
	local result, period, length = cstr.empty, cstr.empty, cstr.empty
	local joinS = ' - '
	
	if eventDateInfo.rDateFull and eventDateInfo.eDateFull then
		period = utils.toDate(eventDateInfo.rDate.timestamp, utils.timeFormat1) .. joinS .. utils.toDate(eventDateInfo.eDate.timestamp, utils.timeFormat1)

		local eventLength = utils.getTimeDiffFormatting(os.difftime(eventDateInfo.eDate.timestamp, eventDateInfo.rDate.timestamp), "day") -- + 1 -- Events start always at 16:00 UTC so even though it covers one more day, the actual length is exactly 7 days for instance
		length = brackets(utils.getDynamicString(eventLength .. space .. utils.getWord(strings.day, eventLength), dayOrder))
		
		result = period .. space .. length
	else
		if eventDateInfo.rDate.fakeMonth or eventDateInfo.eDate.fakeMonth then
			period = eventDateInfo.rDate.year
			if eventDateInfo.rDate.year ~= eventDateInfo.eDate.year then
				period = period .. joinS .. eventDateInfo.eDate.year
			end
		elseif eventDateInfo.rDate.fakeDay or eventDateInfo.eDate.fakeDay then
			period = utils.resolveDateTime(event.rDate, true)
			if eventDateInfo.rDate.month ~= eventDateInfo.eDate.month then
				period = period .. joinS .. utils.resolveDateTime(event.eDate, true)
			end
		end
		result = period
	end
	
	return note(result)
end

function getEventNameWithLink(event)
	for _, excludedEvent in ipairs(_excludedEventList) do --excluding means we won't modify the name as a Link
		if string.find(event.name, excludedEvent) or event.name == excludedEvent then
			return event.name
		end
	end
	--we have a list of string that if they are contained in whole string we Linkify that certain part
	--Example: August 2022 Blood Rush => August 2022 [[Blood Rush]]
	for _, eventItem in ipairs(_eventList) do 
		if string.find(event.name, eventItem) then
			return event.name:gsub(eventItem, link(eventItem))
		end
	end
	
	return link(event.name, event.displayName)
end

function getEventType(event)
	event = table.copy(event)
	if event.tags and #event.tags > 0 then
		for _, tag in ipairs(event.tags) do
			return strings[tag] --if there is at least one tag it will be used as a category
		end
	end
	return event.name
end

function p.getCharTypeWord(el, plural)
	if type(el) == types.string then
		el = {charType = el}
	end
	if el and el.charType then
		if el.charType == 'K' then
			return (plural and strings.killers) or strings.killer
		elseif el.charType == 'S' then
			return (plural and strings.survs) or strings.surv
		end
	end
	return false
end

--return: filename, [sucess]
function p.resolveCharacterThemeMusic(character, returnFilename)
	local dlcs = require("Module:DLCs" .. utils.lang())
	character.dlc = dlcs.getMainDlc(dlcs.getDlcs(character))

	local fileConst = '_Theme_Music'
	local survConst = '_Survivors'
	local filename = cstr.empty
	if not character.dlc then
		filename = character.techName or character.name
	else
		filename = utils.resolveFileName(utils.CapitalizeName(utils.RemoveSpecialCharacters(character.dlc.name)), true, true)
	end
	filename = (filename or cstr.empty) .. ((not utils.isKiller(character) and survConst) or cstr.empty) .. fileConst
	if character.dlc then
		for _, theme in ipairs(dlcThemes) do
			if theme.id == character.dlc.id then
				filename = theme.fileName
			end
		end
	end
	local valid = utils.isValidFileName(filename, cstr.ogg)
	return (valid and file(filename .. dot .. cstr.ogg)) or (returnFilename and filename .. dot .. cstr.ogg) or valid, (returnFilename and valid) or nil
end

function p.shouldHaveTheme(character)
	local dlcs = require("Module:DLCs" .. utils.lang())
	charDlc = dlcs.getMainDlc(dlcs.getDlcs(character))
	return character.dlc and ((charDlc and charDlc.id and charDlc.id > _bothThemesStartingDlcId) or (type(character.dlc) == types.number and character.dlc > _bothThemesStartingDlcId)) or false
end

--element must have either survivor = # or killer = #
function p.getCharacter(element)
	local data = require("Module:Datatable" .. utils.lang())
	if element.survivor then
		return utils.getCharacterById(element.survivor, data.survivors)
	else
		return utils.getCharacterById(element.killer, data.killers)
	end
end

function p.getCharacterByName(name)
	return utils.getCharacterByName(name)
end

function p.getCharacterFirstName(character, technical)
	return utils.getCharacterFirstName(character, technical)
end

function p.getCharacterLastName(character)
	character = utils.resolveParameter(character)
	if not character.name then
		character = p.getCharacterByName(character)
	end
	
	local isKiller = utils.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
	
	text = string.split(text, cstr.space)
	return text[#text] --return last word separated by space
end

function p.getCharactersPronoun(character)
	local langs = require("Module:Languages")
	return langs.evaluatePronoun(character)
end

function p.getCharPortrait(character, mainSize, hover, bgClr)
	local langs = require("Module:Languages")
	bgClr = bgClr or utils.resolveParameter(character, "bgClr", true) or utils.resolveParameter(character, 4, true) or nil
	hover = utils.bool(hover or utils.resolveParameter(character, "hover", true) or utils.resolveParameter(character, 3, true) or false)
	mainSize = tonumber(mainSize or utils.resolveParameter(character, "size", true) or utils.resolveParameter(character, 2, true)) or 250
	character = (character and not character.args and character.name and character) or p.getCharacterByName(utils.resolveParameter(character, "character", true) or utils.resolveParameter(character, "name", true) or utils.resolveParameter(character))
	
	local hoverString, techDisplayName
	local displayName = p.getDisplayName(character)
	if langs.nonEn() then
		techDisplayName = p.getTechDisplayName(character)
	end
	
	local portraitFilename = --"K01 TheTrapper Portrait.png"
		utils.getCharacterIdentifier(character) .. space .. 
		utils.RemoveSpecialCharacters(string.replace(techDisplayName or displayName, space, cstr.empty), true, true) .. space ..
		"Portrait.png"
		
	if hover then
		local loadoutM = require("Module:Loadout" .. utils.lang())
		local perkList = loadoutM.getPerkObjectsByOwner(character.name) or {}
		utils.sortPerksByLevel(perkList)
		local perkSize = mainSize * 0.192 --48 is 19.2% of 250% being a default sizing

		hoverString = 
			'<div class = "charPortraitHoverWrapper ' .. (#displayName > 20 and "charProtraitLongName" or cstr.empty) .. ' flex flexColumn absolute">' .. nl ..
				'<div class = "charPortraitName">' .. link(displayName, utils.specialUpperCase(displayName)) .. '</div>' .. nl .. --[[The Trapper|THE TRAPPER]]
				((not table.empty(perkList) and '<div class = "charPortraitPerkWrapper flex">' .. nl) or cstr.empty)
				
				for _, perk in ipairs(perkList) do 
					hoverString = hoverString .. 
						'<div class = "charPortraitPerk">' .. utils.assembleImage("sosPerk", perk.name, perkSize) .. '</div>' .. nl      --{{#Invoke:Utils|assembleImage|sosPerk|Unnerving Presence|size=48}}
				end
				
			hoverString = hoverString ..
				((not table.empty(perkList) and '</div>' .. nl) or cstr.empty) ..
			'</div>' .. nl
	else
		hoverString = cstr.empty
	end
	result = 
		'<div style = "--defaultCharPortraitDimensions: ' .. mainSize .. 'px;" class = "charPortraitWrapper">' .. nl ..
			'<div class = "charPortraitShadowBG"></div>' .. nl ..
			'<div class = "charPortraitBg"></div>' .. nl ..
			'<div class = "charPortraitRoleBg ' .. (bgClr or (utils.isKiller(character) and "killer" or "survivor")) .. '"></div>' .. nl ..
			'<div class = "charPortraitImage flex flexCenter">' .. file(portraitFilename, "link=" .. displayName) .. '</div>' .. nl .. --[[File:K01 TheTrapper Portrait.png|The Trapper]]
			--'<div class = "charPortraitImage flex flexCenter">' .. utils.getPoisitionedSprite(character.id, mainSize, "SurvivorPortraits") .. '</div>' .. nl .. --[[File:K01 TheTrapper Portrait.png|The Trapper]]
			 hoverString ..
		'</div>' .. nl
			
	return result
end
	
--<div class = "charPortraitWrapper">
--  <div class = "charPortraitShadowBG"></div>
--  <div class = "charPortraitBg"></div>
--  <div class = "charPortraitRoleBg killer"></div>
--  <div class = "charPortraitImage flex flexCenter">[[File:K01 TheTrapper Portrait.png|The Trapper]]</div>
--  <div class = "charPortraitHoverWrapper flex flexColumn absolute">
--    <div class = "charPortraitName">'''[[The Trapper|THE TRAPPER]]'''</div>
--    <div class = "charPortraitPerkWrapper flex">
--      <div class = "charPortraitPerk">{{#Invoke:Utils|assembleImage|sosPerk|Unnerving Presence|size=48}}</div>
--      <div class = "charPortraitPerk">{{#Invoke:Utils|assembleImage|sosPerk|Brutal Strength|size=48}}</div>
--      <div class = "charPortraitPerk">{{#Invoke:Utils|assembleImage|sosPerk|Agitation|size=48}}</div>
--    </div>
--  </div>
--</div>
function p.resolveCharactersTable(charType)
	local data = require("Module:Datatable" .. utils.lang())
	charType = utils.resolveParameter(charType, "charType", true) or utils.resolveParameter(charType)
	local result = cstr.empty

	for _, character in ipairs(charType == 'K' and data.killers or data.survivors) do
		local displayName = p.getDisplayName(character)
		result = result .. 
			'<div style = "display: inline-flex; flex-direction: column; text-align:center; margin-bottom: 35px;">' .. link(character.shortName or character.realName or character.name) .. (((utils.isKiller(character) or character.moniker) and displayName) or cstr.empty) .. nl ..
				p.getCharPortrait(character, nil, false) .. nl ..
			'</div>' .. nl
	end

	return '<div style = "color: #fff;">' .. result .. '</div>'

end

function p.resolveCharactersEntries(charType)
	local data = require("Module:Datatable" .. utils.lang())
	local various = require("Module:Various" .. utils.lang())
	local loadoutM = require("Module:Loadout" .. utils.lang())
	local result = cstr.empty
	charType = utils.resolveParameter(charType, 1, true) or utils.resolveParameter(charType, "charType", true) or 'S'
	
	for tempIndex, character in ipairs(charType == 'K' and data.killers or data.survivors) do
		local currentEntry = cstr.empty
		local charPerks = loadoutM.getPerkObjectsByOwner(character.name)
		if charPerks then
			utils.sortPerksByLevel(charPerks)
		end
		
		currentEntry = 
			'|+ ' .. various.getDisplayName(character) .. nl .. ntl .. nl ..
			tl .. 'class = "center" rowspan = 3' .. tl .. various.getCharPortrait(character, 200, false) .. nl
	
		if type(charPerks) == types.table then
			for i, perk in ipairs(charPerks) do
				currentEntry = currentEntry .. 
					hl .. img(perk.name, "32px") .. nl ..
					hl .. link(perk.name) .. nl ..
					((i < #charPerks and ntl .. nl) or cstr.empty)
			end
		end
		result = result ..
			'<div class = "entryTable">' .. nl ..
				utils.wrapBasicTable(currentEntry, nil, nil, true) .. nl ..
			'</div>' .. nl
	end
	
	result =
		'<div style="display: flex; flex-flow: row wrap; justify-content:center;">' .. nl ..
			result .. nl ..
		'</div>' .. nl
	
	return result
end

function p.getDisplayName(character)
	return 
		(character.moniker and the(character) .. character.moniker) or
		((character.isKiller or utils.isKiller(character)) and the(character) .. character.name) or
		character.shortName or
		character.name
end

--for subwikis
function p.getTechDisplayName(character)
	return 
		(character.techMoniker and "The" .. space .. character.techMoniker) or
		((character.isKiller or utils.isKiller(character)) and (character.techName and "The" .. space .. character.techName)) or
		character.techShortName or
		character.techName
end

--copy of utils version
function p.getCharsByDlc(dlc, charType)
	local data = require("Module:Datatable" .. utils.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 (type(character.dlc) == types.table and table.contains(character.dlc, dlc.id)) or character.dlc == dlc.id then
			table.add(result, 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 (type(character.dlc) == types.table and table.contains(character.dlc, dlc.id)) or character.dlc == dlc.id then
			table.add(result, character)
		end
	end
	return result
end

function getAchievImage(achievement, prefix)
	if achievement and type(achievement) == types.table and achievement.image then
		return achievement.image
	end
	return ((achievement and type(achievement) == types.table and achievement.name or type(achievement) == types.string) and
		"Ach " .. utils.firstLetterLower((prefix or cstr.empty) .. utils.resolveFileName(type(achievement) == types.string and achievement or achievement.techName or achievement.name, false, true)) .. dot .. cstr.jpg) or cstr.empty
end

function getAchieveTableRow(achievement)
	return (achievement and achievement.name and achievement.desc and achievement.image and 
		nl .. ntl .. nl .. tl .. file(achievement.image, "64px") .. nl .. hl .. achievement.name .. nl .. tl .. achievement.desc) or cstr.empty
end

function p.resolveCharacterAchievements(character)
	character = p.getCharacterByName(utils.resolveParameter(character))
	if not character then return false end
	character.isKiller = utils.isKiller(character)
	local dataAchiev = require("Module:Datatable/Achievements" .. utils.lang())
	local result = cstr.empty
	setElementTable("achievements")
	
	if not character then return strings.characterNotFound end
	
	result = result .. getAchieveTableRow(p.getAdeptAchievement(character)) --adding adept achievement
	for achName, ach in pairs(dataAchiev.achievements) do
		if (character.isKiller and ach.killer or ach.survivor) == character.id then
			local achiev = table.copy(ach)
			achiev.name = achiev.name or achName
			
			achiev.image = getAchievImage(achiev)
			p.postprocessAchievementDescription(achiev)
			p.postprocessDescription(achiev)
			result = result .. getAchieveTableRow(achiev)
		end
	end
	
	result = hl .. strings.icon .. dhl .. strings.name .. dhl .. strings.desc .. result .. nl --adding header row
	result = utils.wrapBasicTable(result)

	return result
end

function p.getAdeptAchievement(character, cat)
	setElementTable("achievements")
	local dlcs = require("Module:DLCs" .. utils.lang())
	local loM = require("Module:Loadout" .. utils.lang())
	local dataAchiev = require("Module:Datatable/Achievements" .. utils.lang())
	
	character.dlc = dlcs.getDlcByCharacter(character) --we need to know if the DLC got retracted, hence an achievement is retired
	character.dlc = (#character.dlc == 1 and character.dlc[1]) or character.dlc
	character.perks = loM.getPerkObjectsByOwner(character.name)
	if character.perks then 
		utils.sortPerksByLevel(character.perks)
	end
	local isKiller = utils.isKiller(character)
	local desc = (isKiller and dataAchiev.strings.adeptAchiev_killer) or dataAchiev.strings.adeptAchiev_surv
	local adeptName = character.moniker or (character.useLastName and p.getCharacterLastName(character)) or p.getCharacterFirstName(character)
	local techAdeptName = (character.useLastName and p.getCharacterLastName(character)) or p.getCharacterFirstName(character, true)
	local achievement = {
		name = strings.adept .. space .. adeptName,
		desc = desc,
		adeptName = adeptName,
		isKiller = isKiller,
		character = character,
		diacritics = character.diacritics,
		retired = (character.dlc and (character.dlc.retracted or character.dlc.achRetired)) or false,
		image = getAchievImage(techAdeptName, "adept")
	}

	if not table.contains({"all", "adept"}, cat) then
		p.postprocessAchievementDescription(achievement)
		p.postprocessDescription(achievement)
	end
	return achievement
end

function getAdeptAchievements(charType, cat)
	local data = require("Module:Datatable" .. utils.lang())
	local elTable = charType == 'K' and data.killers or data.survivors
	local result = {}
	for _, character in ipairs(elTable) do
		table.add(result, p.getAdeptAchievement(character, cat))
	end
	return #result > 0 and result or false
end

function p.resolveMapAchievements(map) --used on map Page
	setElementTable("achievements")
	local dataAchiev = require("Module:Datatable/Achievements" .. utils.lang())
	local mapsLogic = require("Module:Maps" .. utils.lang())
	local result = cstr.empty
	local achievs = {}
	map = mapsLogic.getMapByMapName(utils.resolveParameter(map))
	if not map then return strings.mapNotFound end
	
	map.realm = mapsLogic.getRealmByMap(map)
	
	for _, achiev in ipairs(getMapsAchievements(map.id)) do
		result = result .. getAchieveTableRow(achiev)
	end
	
	result = hl .. strings.icon .. dhl .. strings.name .. dhl .. strings.desc .. result .. nl --adding header row
	result = utils.wrapBasicTable(result)

	return result
	
end

function getMapAchievements(mapId)
	local data = require("Module:Datatable" .. utils.lang())
	local dataAchiev = require("Module:Datatable/Achievements" .. utils.lang())
	local result = {}
	
	for _, achiev in ipairs(dataAchiev.achievements) do
		local mapIdList = (achiev.map and type(achiev.map) == types.number and {achiev.map}) or (achiev.map and type(achiev.map) == types.table and achiev.map) or {}
		for _, id in ipairs(mapIdList) do
			if (mapId and id == mapId) or (not mapId and id) then
				achiev.image = getAchievImage(achiev)
				table.add(result, achiev)
			end
		end
	end
	return result
end

function p.getMapAchievement(map)
	setElementTable("achievements")
	local dataAchiev = require("Module:Datatable/Achievements" .. utils.lang())
	
	p.fillElementInfo(map) --fill realm info
	local achievement = {
		name = map.achievName,
		desc = dataAchiev.strings.landmarkGenAchiev,
		image = getAchievImage(map.techAchievName or map.achievName),
		retired = map.retired or (map.realm and (map.realm.retired or map.achRetired)) or false,
		map = map
	}
	
	p.fillElementInfo(achievement)
	p.postprocessAchievementDescription(achievement)
	p.postprocessDescription(achievement)
	
	return achievement
end

function getMapsAchievements(mapId)
	local data = require("Module:Datatable" .. utils.lang())
	local result = {}
	for _, map in ipairs(data.maps) do
		if (mapId and map.id == mapId) or (not mapId and map.achievName) then
			table.add(result, p.getMapAchievement(map))
		end
	end
	table.addRange(result, getMapAchievements(mapId))
	return result
end

function p.getAchievementDescriptionByName(achName)
	local dataAchiev = require("Module:Datatable/Achievements" .. utils.lang())
	achName = utils.resolveParameter(achName)
	for _, ach in ipairs(dataAchiev.achievements) do
		if ach.name == achName then
			p.postprocessAchievementDescription(ach)
			return ach.desc
		end
	end
	return strings.noDesc
end

function p.getAchievementsByCategory(cat, charType)
	setElementTable("achievements")
	local dataAchiev = mw.loadData("Module:Datatable/Achievements" .. utils.lang())
	local achievs = {}
	
	if cat == "adept" or (cat == "all" and charType) then
		table.addRange(achievs, getAdeptAchievements(charType, cat))
	elseif cat == "retired" or cat == "all" then
		table.addRange(achievs, getAdeptAchievements("S", cat))
		table.addRange(achievs, getAdeptAchievements("K", cat))
	end
	if cat == "map" or cat == "retired" or cat == "all" then
		table.addRange(achievs, getMapsAchievements())
	end
	table.addRange(achievs, p.getElementsByCategory(cat, dataAchiev.achievements, charType))
	achievs = table.distinct(achievs)

	if cat == "all" then return achievs end --in this case we have all we need
	
	--utils.sortItemsByName(achievs)
	for _, achiev in ipairs(achievs) do
		p.fillElementInfo(achiev)
		p.postprocessAchievementDescription(achiev, true)
		p.postprocessDescription(achiev)
	end
	
	return achievs
end

function p.getAchievementsCount(cat, charType)
	setElementTable("achievements")
	local achData = mw.loadData("Module:Datatable/Achievements" .. utils.lang())
	charType = charType or utils.resolveParameter(cat, 2, true)
	cat = utils.resolveParameter(cat, 1)
	
	local achievs = p.getAchievementsByCategory(cat, charType)
	local excludedAchievs = {}
	for achievName, achiev in pairs(achievs) do
		if achiev.retired then 
			table.add(excludedAchievs, achievName) 
		end
	end

	return #achievs - #excludedAchievs
end

--hideSpecial is considered achievements that are hidden or retired
function p.resolveAchievementsByCategory(cat, charType, hideSpecial)
	setElementTable("achievements")
	hideSpecial = utils.bool(hideSpecial or utils.resolveParameter(cat, "hideSpecial", true) or utils.resolveParameter(cat, 3, true) or false)
	charType = charType or utils.resolveParameter(cat, 2, true)
	cat = utils.resolveParameter(cat, 1)
	local achievs = p.getAchievementsByCategory(cat, charType)
	utils.sortItemsByName(achievs)
	local result = cstr.empty

	for _, ach in ipairs(achievs) do
		if 
			(not hideSpecial and not ach.retired) or
			(	(	cat ~= "retired"
					and	((cat ~= "hidden" and not ach.hidden and not ach.retired) or (cat == "hidden" and ach.hidden and not ach.retired)) 
					and ((cat ~= "map" and not ach.map and not ach.retired) or (cat == "map" and ach.map and not ach.retired))
				)
				or	(cat == "retired" and ach.retired)
			) then
			result = result .. 
				ntl .. nl ..
				hl .. file(getAchievImage(ach), "64px") .. nl .. 
				hl .. (ach.name or strings.missingName) .. nl ..
				tl .. (ach.desc or strings.noDesc) .. nl
		end
	end
	
	result = utils.wrapBasicTable(result, nil, nil, true)
	return result
end

function p.postprocessAchievementDescription(achievement, skipPerks)
	if achievement.character then
		achievement.desc = string.replace(achievement.desc, "#char", link((achievement.isKiller and achievement.character.name) or achievement.character.shortName or achievement.character.name))
		achievement.desc = string.replace(achievement.desc, "#pronoun", p.getCharactersPronoun(achievement.character))
	end
	if achievement.perks or (achievement.character and achievement.character.perks) then
		local p = achievement.perks or achievement.character.perks
		achievement.desc = string.replace(achievement.desc, "#p1", (skipPerks and utils.IconLink(p[1].name)) or utils.loadoutIconLink(p[1].name, nil, nil, {perk = p[1]})) --utils.IconLink(p[1].name))
		achievement.desc = string.replace(achievement.desc, "#p2", (skipPerks and utils.IconLink(p[2].name)) or utils.loadoutIconLink(p[2].name, nil, nil, {perk = p[2]})) --utils.IconLink(p[2].name))
		achievement.desc = string.replace(achievement.desc, "#p3", (skipPerks and utils.IconLink(p[3].name)) or utils.loadoutIconLink(p[3].name, nil, nil, {perk = p[3]})) --utils.IconLink(p[3].name))
	end
	if achievement.map and achievement.map.landmark then
		achievement.desc = string.replace(achievement.desc, "#landmark", achievement.map.landmark)
	end
	if achievement.map and achievement.map.name then
		achievement.desc = string.replace(achievement.desc, "#map", achievement.map.name)
	end
end

function p.postprocessDescription(element)
	if element.descProcessed then return end
	--can be thrownchanged to loop, with flags = {"deprecated", "mobile", "notAvailable", "retired"}
	if element.hidden then element.desc = string.replace(strings.hidden, "#element", p.elementStringMapper.decom()) .. dnl .. element.desc end
	if element.unknownDesc then element.desc = string.replace(strings.unknownDesc, "#element", p.elementStringMapper.unknownDesc()) .. dnl .. element.desc end
	if element.deprecated then element.desc = string.replace(strings.deprecated, "#element", p.elementStringMapper.deprecated()) .. dnl .. element.desc end --currently replace feature is not used in deprecated string
	if element.mobile then element.desc = string.replace(strings.mobile, "#element", p.elementStringMapper.mobile()) .. dnl .. element.desc end
	if element.notAvailable then element.desc = string.replace(strings.notAvailable, "#element", p.elementStringMapper.notAvailable()) .. dnl .. element.desc end
	if element.unused then element.desc = string.replace(strings.unused, "#element", p.elementStringMapper.unused()) .. dnl .. element.desc end
	if element.decom then element.desc = string.replace(strings.decom, "#element", p.elementStringMapper.decom()) .. dnl .. element.desc end
	if element.retired then element.desc = string.replace(strings.retired, "#element", p.elementStringMapper.retired()) .. dnl .. element.desc end

	
	--subNames(element)
	if element.wip then --wip override ptb, having bigger urgency
		wrapWipBox(element)
	else
		wrapPtb(element) --ptb should not appear if wip is true
	end
	element.descProcessed = true
end

function p.resolveCurrentEvents()
	local currentEvents = p.getLiveEventsList()
	local result = cstr.empty
	
	if not currentEvents then return end
	
	for i, event in ipairs(currentEvents) do
		local eventTome = getEventTome(event)
		local eventLink = (event.tome and not event.event and (event.modifier and strings.modifierTome or strings.tome) .. space .. event.tome .. ' - ' .. event.name) or event.name -- [Modifier] [[Tome 17 - Commitment]] OR simply [[event name]]
		if not (eventTome and eventTome.name == event.name and event.type == "event" and eventTome.modifier) then
			result = result .. 
				(i > 1 and tnl or cstr.empty) ..
				'<big>' .. 
					b(center(
						(
							(event.type == "dlc" and strings.newDlcReleased .. colon .. space)
						or	(event.type ~= "patch" and link(eventLink, event.displayName))
						or	(event.type == "patch" and file(utils.getIcon(strings.patch), "48px") .. space .. strings.new .. space .. utils.getPatchLink(event)) .. space .. file(utils.getIcon(strings.patch), "48px")
						or cstr.empty) ..
						((event.tags and event.tags[1] and event.tags[1] ~= "modifier" and strings[event.tags[1]] and strings[event.tags[1]] ~= event.name and colon .. space .. strings[event.tags[1]]) or cstr.empty) .. 
						((eventTome and eventTome.event and ' + ' .. link(strings.eventTome .. space .. eventTome.tome .. ' - ' .. eventTome.name, strings.eventTome)) or cstr.empty)
						.. brnl ..
						(
							(event.img and file(event.img, "400px", "center", "border", "link=" .. event.name))
							or (event.tome and file("Tome" .. event.tome .. space .. (event.techName or event.name) .. " Banner" .. dot .. cstr.jpg, "400px", "center", "border", "link=" .. eventLink))
							or cstr.empty
							)
						)
					) ..
				'</big>'
		end
	end
	
	return '<div class="fpbox center">' .. nl ..
		'<div class="heading">' .. strings.currentEvents .. '</div>' .. nl ..
		result .. nl ..
	'</div>'
end

function getEventTome(event)
	local data = require("Module:Datatable" .. utils.lang())
	for _, tome in ipairs(data.tomes) do
		if tome.name == event.name then
			return tome
		end
	end
	return false
end

function p.getLiveEventsList()
	local data = require("Module:Datatable" .. utils.lang())
	local result = {}
	
	for _, patch in ipairs(data.patches) do
		patch.eDate = (utils.isFullDateTime(patch.rDate) and utils.addTime("day", 7, patch.rDate)) or nil --to simulate period when this annoucment should appear
		if utils.isEventLive(patch) then
			patch.type = "patch"
			table.add(result, patch)
			break
		end
	end
	local dlcsLogic = require("Module:DLCs" .. utils.lang())
	for _, dlc in ipairs(data.dlcs) do
		dlc.eDate = (utils.isFullDateTime(dlc.rDate) and utils.addTime("day", 14, dlc.rDate)) or nil --to simulate period when this annoucment should appear
		if utils.isEventLive(dlc) then
			dlc.type = "dlc"
			dlc.img = dlcsLogic.resolveDlcCapsuleFileNameByDlc(dlc, dlcsLogic.dlcMinIdForCapsuleCheck)
			table.add(result, dlc)
		end
	end
	
	for _, event in ipairs(data.events) do
		if utils.isEventLive(event) then
			event.type = "event"
			table.add(result, event)
		end
	end
	
	for _, tome in ipairs(data.tomes) do
		if utils.isEventLive(tome) then
			tome.type = "tome"
			table.add(result, tome)
		end
	end
	return (table.empty(result) and false) or result
end

function p.getTomeTable(tomeType)
	tomeType = (type(tomeType) == types.string and tomeType) or utils.resolveParameter(tomeType, 1, true) or "default"
	local result = cstr.empty
	local groupedTomes = getGroupedTomes(tomeType)

	for year, tomesInYear in spairs(groupedTomes) do
		--if table.count(tomesInYear) > 0 then --if there is tomeType == event or modifier we need to skip years without such tomes
			result = result ..
				'<div class = "yearSectionWrapper divTable displayFlex flexColumn ' .. tomeType .. space .. string.replace("tomeYear#", "#", year) .. '">' ..
					'<div class = "yearHeader displayFlex divTableHeader center">' .. big(year) .. '</div>' ..
					'<div class = "tomesInYearWrapper displayFlex">'
						for i = 1, 4 do
							local tome = tomesInYear[i]
							if tome and (tome.quarter == i or tome.dateObj.quarter == i) then
						--for _, tome in ipairs(tomesInYear) do
						--	if		(tomeType == "default" and not (tome.event or tome.modifier))
						--		or	(tomeType == "event" and tome.event)
						--		or	(tomeType == "modifier" and tome.modifier) then
								
								local tomePrefix = (tome.modifier and strings.modifier .. space or cstr.empty) .. (tome.event and strings.event .. space or cstr.empty)
								result = result ..
									'<div class = "tomeWrapper displayFlex flexColumn center">' ..
										'<div class = "tomeIndex ' .. string.replace("tome#", '#', tome.tome or '0') .. ' divTableCell">' .. small(tomePrefix .. strings.tome .. space .. tome.tome) .. '</div>' ..
										'<div class = "tomeName divTableCell">' .. link(tomePrefix .. strings.tome .. space .. tome.tome .. space .. dash .. space .. tome.name, string.upper(tome.name)) .. '</div>' ..
										'<div class = "tomeImg divTableCell displayFlex flexCenter">' .. file((tome.event and "Event" or cstr.empty) .. "Tome" .. tome.tome .. space .. (tome.techName or tome.name) .. space .. "Banner.jpg", "350px") .. '</div>' ..
										'<div class = "tomeRDate divTableCell">' .. utils.resolveDateTime(tome.rDate) .. '</div>' ..
									'</div>'
							else
								result = result .. '<div class = "tomeWrapper blankSpace displayFlex flexColumn center"></div>'
							end
						--	end
						end
				result = result ..
					'</div>' ..
				'</div>'
		--end
	end
	
	result = 
		'<div class = "displayFlex flexCenter>' ..
			'<div class = "tomeTablekWrapper displayInlineFlex flexColumn">' .. result .. '</div>' ..
		'</div>'
	return result
end

function getGroupedTomes(tomeType)
	local data = require("Module:Datatable" .. utils.lang())
	local result = {}
	local latestYear = 0
	for _, tome in ipairs(data.tomes) do
		if		(tomeType == "default" and not (tome.event or tome.modifier))
			or	(tomeType == "event" and tome.event)
			or	(tomeType == "modifier" and tome.modifier) then
				
			tome.dateObj = utils.standardiseDateObject(tome.rDate)
			if not result[tome.dateObj.year] then
				result[tome.dateObj.year] = {}
			end
			lg(tome.name .. space .. dash .. space .. tome.quarter)
			result[tome.dateObj.year][tome.quarter or tome.dateObj.quarter] = tome
		end
	end
	--utils.sortByKeys(result)
	--for year, group in spairs(result) do
	--	utils.sortByRDate(group)
	--end
	
	return result
end

function p.getCharacters_mainPage(charType, size)
	size = tonumber(size or utils.resolveParameter(charType, "size", true) or utils.resolveParameter(charType, 2, true)) or 250
	local data = require("Module:Datatable" .. utils.lang())
	charType = utils.resolveParameter(charType, "charType", true) or utils.resolveParameter(charType)
	local elTable
	
	if charType ~= 'K' and charType ~= 'S' then
		return strings.missingCharType
	else
		elTable = (charType == 'K' and data.killers) or data.survivors
	end

	local result = cstr.empty
	for _, character in ipairs(elTable) do
		result = result ..
			p.getCharPortrait(character, size, true)
	end
	
	local header = 
		((charType == 'K' and the(strings.killers) .. space .. link(strings.killers) .. space .. img(strings.killer, '32px')) or
			the(strings.survs) .. space .. link(strings.survs) .. space .. img(strings.surv, '32px'))
	
	result = 
		--'<div class = "fpbox" style = "text-align: center;">' .. nl ..
			--'<div class = "heading">' .. header .. '</div>' .. nl ..
			'<div class = "charPortraitsMainPageWrapper">' .. nl ..
				result ..
			'</div>' .. nl --..
		--'</div>'
	
	return result
end

return p