Dead by Daylight Wiki

A documentação para este módulo pode ser criada na página Módulo:Cosmetics/doc

local p = {}
local utils = require("Module:Utils")
local data = require("Module:Datatable" .. utils.lang())
local cosData = require("Module:Datatable/Cosmetics/Arbiter" .. utils.lang())
local str = require("Module:Strings")
local killersM = require("Module:Killers")
local survM = require("Module:Survivors")
local frame = mw.getCurrentFrame()

p.strings = {
	notFound = "Não Encontrado!",
	noCosmetics = "No cosmetics found. Cosmetics data probably weren't updated yet." .. cstr.contact,
	cosmetics = "Cosméticos",
	character = "Personagem",
	gender = "Gênero",
	name = "Nome",
	origin = "Origem",
	actor = "Dublador(a)",
	
	prestigeReward = "Recompensa de Nível de Prestígio #plv", --#plv = prestige level
	pCharmDesc = "Uma lembrança recebida ao dominar uma habilidade específica.",

	expand = "Expandir",
	collapse = "Colapsar",
	
	thumbnail = "Miniatura",
	contains = "Contém",
	collectionName = "Nome da Coleção",
	releaseDate = "Data de Lançamento",
	modifier = "Modificador",
	tome = "Livro",
	price = "Preço",
	desc = "Descrição",
	riftTier = "Nível da Fenda",
	
	piece = "Peça",
	collection = "Coleção",
	
	heads = "Heads",
	masks = "Masks",
	hairs = "Hairs",
	torsos = "Torsos",
	legs = "Legs",
	bodies = "Bodies",
	weapons = "Weapons",
	arms = "Arms",
	hands = "Hands",
	upperBodies = "Upper Bodies",
	
	--Collections indicating survivors'/killers' collections (these collections are sorted special way)
	essentials = "Essentials",
	aberrations = "Aberrations"
}
local strings = p.strings
----------------------------------------------
if utils.lang() ~= cstr.empty and (counter or 0) < 1 then
	counter = (counter or 0) + 1
	strings = require("Module:Cosmetics" .. utils.lang()).strings
end

p.bodyPositions_killer = {
	[1] = {heads = "heads", masks = "masks", hairs = "heads", upperBodies = "upperBodies", arms = "arms", hands = "hands"},
	[2] = {legs = "legs", bodies = "bodies", torsos = "torsos"},
	[3] = {weapons = "weapons"}
}
p.bodyPositions_survivor = {
	[1] = {heads = "heads", masks = "masks", hairs = "heads", arms = "arms", hands = "hands"},
	[2] = {torsos = "torsos", bodies = "bodies", upperBodies = "upperBodies"},
	[3] = {legs = "legs", weapons = "weapons"}
}

local cosCategories = {
	store = "store",
	rift = "rift"
}

function p.getAllOutfitsForChar(character)
	character = utils.getCharacterByName(utils.resolveParameter(character, "character", true) or utils.resolveParameter(character))
	if not character then return false end
	local outfitList = {}
	local fakeOutfits = {}
	
	character.charTypeName = (utils.isKiller(character) and "killer") or "survivor"
	
	for _, cosmetic in ipairs(cosData.outfits) do
		if character.id ==  cosmetic[character.charTypeName] then
			if cosmetic.fakeOutfit then
				table.insert(fakeOutfits, table.copy(cosmetic)) --we must create a COPY of the cosmetic as the data are loaded via mw.loadData which is ReadOnly
			else
				table.insert(outfitList, table.copy(cosmetic))
			end
		end
	end
	
	for i, cosmetic in ipairs(outfitList) do
		cosmetic.pieces = getUnderlyingPieces(cosmetic)
	end
	
	for i, cosmetic in ipairs(fakeOutfits) do
		cosmetic.pieces = getUnderlyingPieces(cosmetic)
	end

	--mw.log(mw.dumpObject(outfitList))
	--mw.log(mw.dumpObject(fakeOutfits))
	return outfitList, fakeOutfits
end

function p.getCosmeticsByName(outfitNames,multiName)
	multiName = multiName or utils.resolveParameter(outfitNames, "multiName", true) or utils.resolveParameter(outfitNames, 2)
	outfitNames = utils.resolveParameter(outfitNames, 1)
	outfitNames = string.replace(outfitNames, '[\n]', cstr.empty)
	outfitNames = (outfitNames and string.split(outfitNames, ',')) or false
	if not outfitNames then return strings.notFound end
	
	
	dmp(outfitNames)
	local outfits = {}
	local fakeOutfits = {}
	
	for _, cosmetic in ipairs(cosData.outfits) do
		if cosmetic.fakeOutfit then
			for piece, cosId in pairs(cosmetic.pieces) do
				if table.contains(outfitNames, cosData[piece][cosId].name) then
					table.insert(fakeOutfits, table.copy(cosmetic)) --we must create a COPY of the cosmetic as the data are loaded via mw.loadData which is ReadOnly
				end
			end
		else
			if table.contains(outfitNames, cosmetic.name) then
				table.insert(outfits, table.copy(cosmetic))
			end
		end
		
		--When this is enabled it will supress any multiName pieces. If we wan to optimise the loop anyway we should used a parameter multiName to know whether we expect multiple cosmetics with same name
		--if #outfitNames == (#outfits + #fakeOutfits) then
		--	break 
		--end
	end
	
	for i, cosmetic in ipairs(outfits) do cosmetic.pieces = getUnderlyingPieces(cosmetic) end
	for i, cosmetic in ipairs(fakeOutfits) do cosmetic.pieces = getUnderlyingPieces(cosmetic) end
	
	outfits = populateTomes(outfits)
	fakeOutfits = populateTomes(fakeOutfits)

	return renderCosmeticTable(outfits, fakeOutfits, true, true)
end

function p.getCosmeticsByCollection(collectionNames)
	local collectionList = utils.resolveParameter(collectionNames, "collectionList", true)
	collectionNames = utils.resolveParameter(collectionNames, 1)
	collectionNames = (collectionNames and string.split(collectionNames, ',')) or false
	if not collectionNames then return strings.notFound end
	
	local outfits = {}
	local fakeOutfits = {}
	
	collectionList = (collectionList and {collectionList}) or getCollections(collectionNames)
	local collectionIdList = table.get(collectionList, "id")
	--dmp(collectionIdList)

	for _, cosmetic in ipairs(cosData.outfits) do
		if cosmetic.fakeOutfit then
			for piece, cosId in pairs(cosmetic.pieces) do
				if table.contains(collectionIdList, cosmetic.collectionId) then
					table.insert(fakeOutfits, table.copy(cosmetic)) --we must create a COPY of the cosmetic as the data are loaded via mw.loadData which is ReadOnly
					fakeOutfits[#fakeOutfits].collection = assignCollection(collectionList, cosmetic.collectionId)
				end
			end
		else
			if table.contains(collectionIdList, cosmetic.collectionId) then
				table.insert(outfits, table.copy(cosmetic))
				outfits[#outfits].collection = assignCollection(collectionList, cosmetic.collectionId)
			end
		end
	end
	
	for i, cosmetic in ipairs(outfits) do cosmetic.pieces = getUnderlyingPieces(cosmetic) end
	for i, cosmetic in ipairs(fakeOutfits) do cosmetic.pieces = getUnderlyingPieces(cosmetic) end
	
	outfits = populateTomes(outfits)
	fakeOutfits = populateTomes(fakeOutfits)

	return renderCosmeticTable(outfits, fakeOutfits, true, true)
end

--sorting list by characters stored in the main datatable - their order stored in the corresponding table is the same as we need
local function sortCosmeticsByCharacters(collectionList, charTable)
	for _, character in ipairs(charTable) do
		for _, collection in ipairs(collectionList) do
			--string.find(collection.name, string.replace(character.name, "%-", "%%-")), in order to SEARCH a "-" sign we have to escape it "%-", and add another percent sign "%%-"
			--so when we call strin.find() it will look at "%-" and interpret it as literally "-" instead of taking a "-" as a quantifier/magic character
			if string.find(collection.name, utils.getCharacterFirstName(character)) or string.find(collection.name, string.replace(character.name, "%-", "%%-")) then
				collection.rate = character.id
				break
			end
		end
	end
	utils.sortByRate(collectionList)
end

function p.getCollectionsContainingString(containStrings)
	containStrings = utils.resolveParameter(containStrings, 1)
	containStrings = string.replace(containStrings, '[\n]', cstr.empty)
	containStrings = (containStrings and string.split(containStrings, ',')) or false
	if not containStrings then return strings.notFound end
	
	local collectionList = {}
	
	for _, collection in ipairs(cosData.collections) do
		for _, containString in ipairs(containStrings) do
			if string.find(collection.name, containString) then
				table.add(collectionList, table.copy(collection)) --We don't do the table list for tabber here because we want to use generic sort function on names eventually
			end
		end
	end
	
	--sorting special collections
	--lg("************************************ BEFORE ************************************")
	--dmp(collectionList)
	if containStrings[1] == strings.essentials then
		sortCosmeticsByCharacters(collectionList, data.survivors)
	elseif containStrings[1] == strings.aberrations then
		sortCosmeticsByCharacters(collectionList, data.killers)
	else
		utils.sortByName(collectionList, true)
	end
	--lg("************************************ AFTER ************************************")
	--dmp(collectionList)
	
	--{{header = 2022, content = "..."}, {}, ...}
	local tabberTable = {}
	for _, collection in ipairs(collectionList) do
		table.add(tabberTable,
			{
				header = collection.name,
				content = p.getCosmeticsByCollection({args={collectionList = collection}})
			}
		)
	end
	return
		'<div class = "tableTabber">' .. nl ..
			utils.getTabberFromTable(tabberTable) .. nl ..
		'</div>'
end


function assignCollection(collectionList, collectionId)
	for _, collection in ipairs(collectionList) do
		if collection.id == collectionId then
			return collection
		end
	end
end

function getCollections(collectionNames)
	result = {}
	
	for _, collection in ipairs(cosData.collections) do
		if table.contains(collectionNames, collection.name, true) then
			table.add(result, collection)	
		end
		if #collectionNames == #result then break end
	end
	
	return result
end

function getUnderlyingPieces(cosmetic)
	local pieceObjects = {}
	
	for key, pieceId in pairs(cosmetic.pieces) do
		--mw.log("cosData[key][pieceId].id == pieceId :: " .. cosData[key][pieceId].id .. " == " .. pieceId)
		if cosData[key][pieceId].id == pieceId then --optimization => before we search for the piece in according body type list we try to find it by index (presuming that index == ID, e.g. we use piece ID as an Index in table and look whether ID of searched piece matches)
			pieceObjects[key] = cosData[key][pieceId]
		else
			for _, categoryPiece in ipairs(cosData[key]) do --go through piece category list (e.g. heads, masks, torsos, etc...)
				if categoryPiece.id == pieceId then
					pieceObjects[key] = categoryPiece
					break --once we find a piece, we can go on the next one since there are no more than one of the same piece type
				end
			end
		end
	end
	
	return pieceObjects
end

function p.resolveCosCharBox(args)
	name = utils.resolveParameter(args)
	cosChar = getCosCharByName(name)
	local portrait--,character
	if not cosChar then return strings.cosmetics .. space .. strings.notFound .. colon .. space .. name end
	--[[if cosChar.killer then
		character = killersM.getKillerById(cosChar.killer)
	else
		character = survM.getSurvivorById(cosChar.survivor)
	end]]
	
	portrait = "CC" .. string.format("%03d", cosChar.id) .. "_charSelect_portrait"
	result = '{| class = "infoboxtable"' .. nl ..
	ntl ..' class = "infoboxTitle" | ' .. nl ..
	'! class = "center bold" colspan = 2 | ' .. ((cosChar.killer and the(cosChar)) or cstr.empty) .. cosChar.name .. nl .. ntl .. nl ..
	'! class = "center" colspan = 2 | ' .. file(portrait .. '.png', '150px') .. nl .. ntl .. nl
	if cosChar.realName ~= nil then
		result = result .. '| class = "titleColumn" | ' .. strings.name .. ' || class = "valueColumn" | ' .. cosChar.realName .. nl .. ntl .. nl
	end
	result = result .. '| class = "titleColumn" | ' .. strings.gender .. ' || class = "valueColumn" | ' .. cosChar.gender .. nl
	if cosChar.origin ~= nil then
		result = result .. ntl .. nl .. '| class = "titleColumn" | ' .. strings.origin .. ' || class = "valueColumn" | ' .. cosChar.origin .. nl
	end
	if cosChar.actor ~= nil then
		result = result .. ntl .. nl .. '| class = "titleColumn" | ' .. strings.actor .. ' || class = "valueColumn" | ' .. cosChar.actor .. nl
	end
	result = result .. '|}'
	
	mw.log(result)
	return result
end


function getCosCharByName(name)
	for _, outfit in ipairs(cosData.cosChars) do
		if cstr.the .. space .. outfit.name == name then
			return outfit
		end
	end
	for _, outfit in ipairs(cosData.cosChars) do
		if outfit.name == name then
			return outfit
		end
	end
end

function getCharactersBodyParts(outfits)
	result = {}

	for _, outfit in ipairs(outfits) do
		for piece, pieceId in pairs(outfit.pieces) do
			local pieceFound = false
			for _, foundPiece in ipairs(result) do
				if foundPiece == piece then
					pieceFound = true
					break
				end
			end
			if not pieceFound then
				table.insert(result, piece)
			end
		end
		if #result == 3 then break end
	end
	
	return result
end

--doesn't have to return all parts
function getOutfitBodyParts(outfit)
	result = {}

	for piece, pieceId in pairs(outfit.pieces) do
		local pieceFound = false
		for _, foundPiece in ipairs(result) do
			if foundPiece == piece then
				pieceFound = true
				break
			end
		end
		if not pieceFound then
			table.insert(result, piece)
		end
	end

	return result
end

function getCosCharType(outfit)
	return (outfit.killer and 'K') or (outfit.survivor and 'S')
end

function getSortedBodyPartsWithIcons(bodyParts, charType)
	local result = {}
	for _, bp in ipairs(bodyParts) do
		for i, category in ipairs((charType == 'K' and p.bodyPositions_killer) or p.bodyPositions_survivor) do --ipairs(p[bodyPositions_ .. ((charType == 'killer' and 'killer') or 'survivor')] ) do
			local foundCategory = false
			for catName, catIcon in pairs(category) do
				if catName == bp then
					result[i] = {[catName] = catIcon}
					--result['"' .. i .. '"'] = {[catName] = catIcon}
					foundCategory = true
					break
				end
			end
			if foundCategory then break end
		end
	end
	return result
end

function sortBodyParts(bodyParts, charType, debugMode)
	local result = {}
	for bpKey, bp in pairs(bodyParts) do
		for i, category in ipairs((charType == 'K' and p.bodyPositions_killer) or p.bodyPositions_survivor) do --ipairs(p[bodyPositions_ .. ((charType == 'killer' and 'killer') or 'survivor')] ) do
			local foundCategory = false
			for catName, _ in pairs(category) do
				if catName == bpKey then
					result[i] = {[bpKey] = bp}
					foundCategory = true
					break
				end
			end
			if foundCategory then break end
		end
	end
	return result
end

function filterOutfits(outfits, category, bodypart)
	local itemsToRemove = {}
	
	if category == cosCategories.store then
		for i, outfit in pairs(outfits) do
			if outfit.fakeOutfit then
				for pieceKey, piece in pairs(outfit.pieces) do
					if (not piece.ac and not piece.is) or piece.tier or not piece.purchasable then --Condition to be REMOVED
						table.insert(itemsToRemove, i)
					end
				end
			elseif not outfit.ac and not outfit.is or not outfit.purchasable then --Condition to be REMOVED
				table.insert(itemsToRemove, i)
			end
		end
		utils.sortTableDesc(itemsToRemove)
		for _, i in ipairs(itemsToRemove) do
			table.remove(outfits, i)
		end
	end

	if category == cosCategories.rift then
		for i, outfit in pairs(outfits) do
			for _, piece in pairs(outfit.pieces) do
				if not piece.tier then
					table.insert(itemsToRemove, i)
					break
				end
			end
		end
		utils.sortTableDesc(itemsToRemove)
		for _, i in ipairs(itemsToRemove) do
			table.remove(outfits, i)
		end
	end
	
	--dmp(table.get(outfits, "name"))
	return outfits --perhaps redesign to not remove it from original. Better way would probably be making a copy and render all categories from one run at once
end

function populateTomes(outfits)
	for _, outfit in pairs(outfits) do
		for _, piece in pairs(outfit.pieces) do
			if piece.tome then
				for _, tome in ipairs(tomes) do
					if piece.tome == tome.tome and not (tome.event or tome.modifier) then
						outfit.tome = tome --let's move the TOME info to outfit object (instead of underlying piece)
						break
					end
				end
				break --first run should suffice to find a Tome
			end
		end
	end
	--dmp(outfits)
	return outfits
end

function p.getPerkCharmsForCharacter(character)
	character = utils.getCharacterByName(utils.resolveParameter(character, "character", true) or utils.resolveParameter(character))
	if not character then return false end
	local isKiller = utils.isKiller(character)
	local charms = {}
	local result = cstr.empty

	for _, charm in ipairs(cosData.charms) do
		if string.find(charm.filename, (isKiller and 'K' or 'S') .. string.format("%02d", character.id)) then
			table.insert(charms, charm)
			if(#charms == 3) then break end
		end
	end
	
	for i = 7, 9 do
		result = result .. hl .. string.replace(strings.prestigeReward, '#plv', i) .. nl
	end
	result = result .. ntl .. nl
	
	utils.sortItemsBycompFilename(charms) --sort it by filename
	for _, charm in ipairs(charms) do
		result = result  .. tl .. class("BG-All CosmeticsBG-All SquareBG-2-enh") .. tl .. file(charm.filename, "256px") .. nl
	end
	result = result ..
		tLine("colspan = 3 " .. class("center"), strings.pCharmDesc)
		
	result = utils.wrapBasicTable(result)
	
	--lg(result)
	return result
end

function p.getOutfitsForCharacter(character, category, bodypart)
	bodypart = bodypart or utils.resolveParameter(character, "bodypart", true)
	category = category or utils.resolveParameter(character, "category", true)
	local outfits, fakeOutfits = p.getAllOutfitsForChar(character)
	if not outfits or table.empty(outfits) then return strings.noCosmetics end
	character = utils.getCharacterByName(utils.resolveParameter(character, "character", true) or utils.resolveParameter(character)) --can be optimized as the getAllOutfitsForChar calls the function too
	
	outfits = filterOutfits(outfits, category, bodypart)
	fakeOutfits = filterOutfits(fakeOutfits, category, bodypart)
	
	if category == cosCategories.rift then
		outfits = populateTomes(outfits)
		fakeOutfits = populateTomes(fakeOutfits)

		--TODO
		--p.getOutfitsForCharacter("Dwight Fairfield", "rift")
		--create a deisgn for fakeOutfits - this will need whole new logic instead of merging it into already existing outfits
	end
	
	return renderCosmeticTable(outfits, fakeOutfits, false, false)
end

function renderCosmeticTable(outfits, fakeOutfits, displayIconForActivePiecesOnly, displayName)
	utils.sortCosmeticsByRarityAndName(outfits)
	utils.sortCosmeticsByRarityAndName(fakeOutfits)

	local result =
		'<div id = "collapseString" style = "display:none;">' .. strings.collapse .. '</div>' .. nl ..
		'<div id = "expandString" style = "display:none;">' .. strings.expand .. '</div>' .. nl
	for _, outfit in ipairs(outfits) do
		if not (outfit.collection and type(outfit.collection) == types.table) then 
			outfit.collection = getCollection(outfit)
		end
		local charType = getCosCharType(outfit)
		local characterBodyParts = getSortedBodyPartsWithIcons((displayIconForActivePiecesOnly and getOutfitBodyParts(outfit)) or getCharactersBodyParts(outfits), charType)

		result = result ..
			'<div class = "cosmeticTable divTable displayFlex flexColumn cosmetic' .. outfit.id .. '">' .. nl ..
				'<div class = "outfitView">' .. nl ..
					'<div class = "cosmeticHeader displayFlex">' .. nl .. --HEADERS
						'<div class = "cosmeticThumbnail divTableHeader">' .. strings.thumbnail .. '</div>' .. nl ..
						'<div class = "cosmeticPieces divTableHeader">' .. strings.contains .. '</div>' .. nl .. -- <!--Variable => "Contains" for outfits, "Piece" for individual piece-->
						'<div class = "cosmeticName divTableHeader">' .. strings.name .. '</div>' .. nl ..
						((displayName and '<div class = "cosmeticCharacter divTableHeader">' .. strings.character .. '</div>' .. nl) or cstr.empty) ..
						'<div class = "cosmeticCollection divTableHeader">' .. strings.collectionName .. '</div>' .. nl .. --<!--optional-->
						((outfit.tome and '<div class = "cosmeticTome divTableHeader">' .. strings.tome .. '</div>' .. nl) or cstr.empty) ..
						'<div class = "cosmeticReleaseDate divTableHeader">' .. strings.releaseDate .. '</div>' .. nl .. --<!--optional?-->
						'<div class = "cosmeticPrice divTableHeader">' .. strings.price .. '</div>' .. nl ..
					'</div>' ..
					generateOutfitTableRow(outfit, characterBodyParts, displayName) ..
					'<div class = "expansionButton">' .. nl ..
						'<div class = "divTableHeader displayFlex flexCenter bold"><span id = "outfit-' .. outfit.id .. '" class = "divButton fakeLink">⇓ ' .. strings.expand .. ' ⇓</span></div>' .. nl .. -- <!-- outfit# => variable unique to outfit --> <!-- Secondary text: ⇑ Collapse ⇑ -->
					'</div>' .. nl ..
				'</div>' .. nl ..
				'<div class = "expandablePart displayFlex flexColumn outfit-' .. outfit.id .. '">'  .. nl -- <!-- outfit# => variable unique to outfit -->
					local pieceIndex = 1
					local sortedPieces = sortBodyParts(outfit.pieces, charType, outfit.id)
					--if outfit.id == 172 then mw.log(mw.dumpObject(sortedPieces)) end
					for i, keyValuePair in pairs(sortedPieces) do
						for pieceType, piece in pairs(keyValuePair) do --data are stored like this: { [1] = {heads = {}}, [2] = {torsos = {}}, [3] = {legs = {}} } - in order to keep order of bodyparts
							local iconLinkPieceName = ((charType == 'K' and p.bodyPositions_killer) or p.bodyPositions_survivor)[i][pieceType] or cstr.empty
							result = result .. generateOutfitPieceRow(iconLinkPieceName, outfit, piece, pieceIndex)
						end
						pieceIndex = pieceIndex + 1
					end
				result = result ..
				'</div>' .. nl ..
			'</div>' .. nl
	end
	for i, outfit in ipairs(fakeOutfits) do
		for pieceType, piece in pairs(outfit.pieces) do
			result = result .. generateOutfitPieceRow(pieceType, outfit, piece, nil, displayName)
	    end
	end
	result = 
		'<div class = "cosmeticWrapper">' .. nl .. 
			result .. nl .. 
		'</div>'
	
	--mw.log(result)
	return result
end

function getDisabledPieces(outfit, characterBodyParts)
	local result = {}
	
	--if outfit.id == 179 --[[354]] --[[437]] then dmp(outfit) lg("*************************") dmp(characterBodyParts) end
	
	for _, keyValuePair in ipairs(characterBodyParts) do
		for bp, _ in pairs(keyValuePair) do
			local pieceFound = false
			for pieceType, _ in pairs(outfit.pieces) do
				if pieceType == bp then
					pieceFound = true
					break
				end
			end
			if not pieceFound then
				table.insert(result, bp)
			end
		end
	end
	
	--if outfit.id == 179 then dmp(result) end
	return result
end

function isPieceDisabled(disabledPieces, piece)
	for _, disabledPiece in pairs(disabledPieces) do
		if disabledPiece == piece then
			return true
		end
	end
	return false
end

--bodypart value
function bpValue(bodypart)
	for key, value in pairs(bodypart) do
		return value
	end
end
--bodypart key
function bpKey(bodypart)
	for key, value in pairs(bodypart) do
		return key
	end
end

function getCollection(outfit)
	if outfit.collectionId then
		for _, collection in ipairs(cosData.collections) do
			if collection.id == outfit.collectionId then
				return collection
			end
		end
	end
end

--example characterBodyParts: { [1] = {hairs = "heads"}, [2] = {bodies = "bodies"}, [3] = {weapons = "weapon"} } => { [pieceCategory] = { bodypartName = "iconToShow"} , ..., ...}
function generateOutfitTableRow(outfit, characterBodyParts, displayName)
	local various = require("Module:Various")
	local result = cstr.empty
	local disabledPieces = getDisabledPieces(outfit, characterBodyParts)
	
	
	--utils.getIcon(FirstLetterUpper(next(characterBodyParts[1])))
	
	local collectionNameString = cstr.empty
	if type(outfit.collection) == types.table and outfit.collection.name then
		collectionNameString = outfit.collection.name
		if string.find(outfit.collection.name, strings.collection) then
			collectionNameString = link(collectionNameString)
		else
			collectionNameString = link(collectionNameString .. space .. strings.collection, collectionNameString)
		end
	end
	
	result = result ..
		'<div class = "cosmeticPiece displayFlex">' .. nl ..
			'<div class = "pieceThumbnail displayFlex flexCenter BG-All CosmeticsBG-All SquareBG-' .. outfit.rarity .. '-enh ' .. ((outfit.visceral and "CosmeticsBG-Visceral") or cstr.empty) .. '">' .. file(outfit.filename, "230px", "link=") ..
				((outfit.linked and nl .. '<div class = "linkedIcon">' .. file("IconHelp cosmetic sets.png", "56px", "link=") .. '</div>') or cstr.empty) ..
			'</div>' .. nl ..
			'<div class = "pieceDetails displayFlex flexColumn">' .. nl ..
				'<div class = "outfitDetailsValues displayFlex flexCenter flexStretch center">' .. nl ..
					'<div class = "outfitPieces divTableCell displayFlex">' .. nl
						for key, value in pairs(characterBodyParts) do
							local kValue = value[next(value)] --next() workarounds when key is associative string (like "hairs": hairs = "heads"). Value[next()] picks value then
							iconName = utils.getIcon(utils.FirstLetterUpper(kValue))
							result = result ..
							'<div class = "outfitSlot' .. key .. ((isPieceDisabled(disabledPieces, kValue) and space .. 'disabledPiece') or cstr.empty) .. '">' .. file(iconName, "56px", "link=") .. '</div>' .. nl
						end
						result = result ..
					'</div>' .. nl ..
					'<div class = "pieceName cellHeader displayFlex flexCenter">' .. outfit.name .. '</div>' .. nl ..
					((displayName and 
						'<div class = "pieceCharacter divTableCell displayFlex flexCenter">' .. nl ..
							'<div class = "iconLinkWrapper">'.. utils.IconLink({args={utils.resolveCharacterIconLinkName(various.getCharacter(outfit)), noTooltip = true}}) .. '</div>' .. nl ..
						'</div>') or cstr.empty) ..
					'<div class = "collectionValue divTableCell displayFlex flexCenter">' .. nl ..
						'<div class = "collectionNameWrapper">' .. collectionNameString .. '</div>' .. nl ..
					'</div>' .. nl ..
					((outfit.tome and '<div class = "tomeValue divTableCell displayFlex flexCenter">' .. link((outfit.tome.modifier and strings.modifier .. space or cstr.empty) .. strings.tome .. space .. outfit.tome.tome .. ' - ' .. outfit.tome.name) .. '</div>' .. nl) or cstr.empty) ..
					'<div class = "releaseDateValue divTableCell displayFlex flexCenter">' .. utils.resolveDateTime(outfit.rDate) .. '</div>' .. nl ..
				'</div>' .. nl ..
				'<div class = "pieceDescriptionHeader divTableHeader">' .. strings.desc .. '</div>' .. nl ..
				'<div class = "pieceDescValue divTableCell displayFlex flexCenter center">' .. outfit.desc .. '</div>' .. nl ..
			'</div>' .. nl ..
			'<div class = "priceContainer displayFlex flexColumn">' .. nl ..
				((outfit.ac and '<div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ACBG">' .. outfit.ac .. file("IconHelp auricCells.png", "48px","link=Auric Cells") .. '</div>' .. nl) or cstr.empty) ..
				((outfit.is and '<div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ISBG">' .. outfit.is .. file("IconHelp iridescentShards.png", "48px","link=Iridescent Shards") .. '</div>' .. nl) or cstr.empty) ..
			'</div>' .. nl ..
		'</div>' .. nl

	return result
end

function generateOutfitPieceRow(pieceKey, outfit, piece, pieceIndex, displayName) --need to pass the key itself: heads = {...}
	local various = require("Module:Various")
	local result = cstr.empty
	local iconName = utils.getIcon(utils.FirstLetterUpper(pieceKey))
	local expandedPartCSS = 'expandedPiece piece' .. (pieceIndex or "error")
	local fakeOutfitCSS = 'cosmeticTable divTable fakeOutfit flexStretch'
	
	if not (outfit.collection and type(outfit.collection) == types.table) then
		outfit.collection = getCollection(outfit)
	end
	
	local collectionNameString = false
	if outfit.collectionId and outfit.collection then
		if string.find(outfit.collection.name, strings.collection) then
			collectionNameString = link(outfit.collection.name)
		else
			collectionNameString = link(outfit.collection.name .. space .. strings.collection, outfit.collection.name)
		end
	end
	
	result = result ..
		'<div class = "' .. ((outfit.fakeOutfit and fakeOutfitCSS) or expandedPartCSS) .. space .. 'displayFlex center">' .. nl ..
			'<div class = "pieceThumbnail displayFlex flexCenter BG-All CosmeticsBG-All SquareBG-' .. outfit.rarity .. '-enh ' .. ((outfit.visceral and "CosmeticsBG-Visceral") or cstr.empty) .. '">' .. file(piece.filename, "230px", "link=") .. '</div>' .. nl ..
			'<div class = "pieceDetailsValues displayFlex flexCenter flexStretch">' .. nl ..
				'<div class = "pieceHeaders displayFlex flexColumn">' .. nl ..
					'<div class = "pieceDetail bodyPartHeader divTableHeader displayFlex flexCenter">' .. strings.piece .. '</div>' .. nl ..
					'<div class = "pieceDetail pieceName divTableHeader displayFlex flexCenter">' .. strings.name .. '</div>' .. nl ..
					((displayName and '<div class = "pieceDetail pieceCharacter divTableHeader displayFlex flexCenter">' .. strings.character .. '</div>' .. nl) or cstr.empty) ..
					((outfit.collectionId and '<div class = "pieceDetail collectionHeader divTableHeader displayFlex flexCenter">' .. strings.collection .. '</div>' .. nl) or cstr.empty) ..
					((outfit.fakeOutfit and outfit.tome and '<div class = "pieceDetail pieceTome divTableHeader displayFlex flexCenter">' .. strings.tome .. '</div>' .. nl) or cstr.empty) ..
					((piece.tier and '<div class = "pieceDetail pieceTier divTableHeader displayFlex flexCenter">' .. strings.riftTier .. '</div>' .. nl) or cstr.empty) .. --<!--optional-->
				'</div>' .. nl
				result = result ..
				'<div class = "pieceHeaders displayFlex flexColumn">' .. nl ..
					'<div class = "pieceDetail bodyPartHeader divTableCell displayFlex flexCenter">' .. file(iconName, "56px", "link=") .. '</div>' .. nl ..
					'<div class = "pieceDetail pieceName divTableCell displayFlex flexCenter">' .. piece.name .. '</div>' .. nl ..
					((displayName and
						'<div class = "pieceDetail pieceCharacter divTableCell displayFlex flexCenter">' .. nl ..
							'<div class = iconLinkWrapper>'.. utils.IconLink({args={utils.resolveCharacterIconLinkName(various.getCharacter(outfit)), noTooltip = true}}) .. '</div>' .. nl ..
						'</div>') or cstr.empty) ..
					((outfit.collectionId and collectionNameString and
						'<div class = "collectionNameWrapper">' .. nl ..
							'<div class = "pieceDetail collectionHeader divTableCell displayFlex flexCenter">' .. collectionNameString .. '</div>' .. nl ..
						'</div>' .. nl) or cstr.empty) .. --todo revisit optimization
					((outfit.fakeOutfit and outfit.tome and '<div class = "pieceDetail pieceTome divTableCell displayFlex flexCenter">' .. link(strings.tome .. space .. outfit.tome.tome .. ' - ' .. outfit.tome.name) .. '</div>' .. nl) or cstr.empty) ..
					((piece.tier and '<div class = "pieceDetail pieceTier divTableCell displayFlex flexCenter">' .. piece.tier .. '</div>' .. nl) or cstr.empty) ..
				'</div>' .. nl
				result = result ..
				'<div class = "pieceDesc divTableCell displayFlex flexColumn flexCenter">' .. (piece.desc or cstr.empty) .. '</div>' .. nl ..
				'<div class = "priceContainer displayFlex flexColumn">' .. nl ..
					((not outfit.linked and piece.ac and '<div class = "ccyPrice displayFlex flexColumn flexCenter BG-All ISACBG-All ACBG">' .. piece.ac .. file("IconHelp auricCells.png", "48px","link=Auric Cells") .. '</div>' .. nl) or cstr.empty) ..
					((not outfit.linked and piece.is and '<div class = "ccyPrice displayFlex flexColumn flexCenter BG-All ISACBG-All ISBG">' .. piece.is .. file("IconHelp iridescentShards.png", "48px","link=Iridescent Shards") .. '</div>' .. nl) or cstr.empty) ..
				'</div>' .. nl ..
			'</div>' .. nl ..
		'</div>' .. nl
	return result
end


-- <div class = "cosmeticTable displayFlex flexColumn cosmetic1"> <!-- cosmetic# => variable unique to outfit -->
  -- <div class = "outfitView">
    -- <div class = "cosmeticHeader displayFlex">
      -- <div class = "cosmeticThumbnail divTableHeader">Thumbnail</div>
      -- <div class = "cosmeticPieces divTableHeader">Contains</div> <!--Variable => "Contains" for outfits, "Piece" for individual piece-->
      -- <div class = "cosmeticName divTableHeader">Name</div>
      -- <div class = "cosmeticCollection divTableHeader">Collection Name</div> <!--optional-->
      -- <div class = "cosmeticReleaseDate divTableHeader">Release Date</div> <!--optional?-->
      -- <div class = "cosmeticPrice divTableHeader">Price</div>
    -- </div>
    -- <div class = "cosmeticPiece displayFlex">
      -- <div class = "pieceThumbnail displayFlex flexCenter BG-All CosmeticsBG-All SquareBG-1-enh">[[File:Dwight outfit 014.png|230px|link=]]</div>
      -- <div class = "pieceDetails displayFlex flexColumn">
        -- <div class = "pieceDetailsValues displayFlex flexCenter flexStretch">
          -- <div class = "outfitPieces divTableCell displayFlex">
            -- <div class = "outfitSlot1 disabledPiece">[[File:CategoryIcon head.png|56px|link=]]</div>
            -- <div class = "outfitSlot2">[[File:CategoryIcon torso.png|56px|link=]]</div>
            -- <div class = "outfitSlot3">[[File:CategoryIcon legs.png|56px|link=]]</div>
          -- </div>
          -- <div class = "pieceName cellHeader displayFlex flexCenter">name</div>
          -- <div class = "collectionValue divTableCell displayFlex flexCenter">Collection Name</div>
          -- <div class = "releaseDateValue divTableCell displayFlex flexCenter">23 September 2022</div>
        -- </div>
        -- <div class = "pieceDescriptionHeader divTableHeader">Description</div>
        -- <div class = "pieceDescValue divTableCell displayFlex flexCenter">desc</div>
      -- </div>
      -- <div class = "priceContainer displayFlex flexColumn">
        -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ACBG">135[[File:IconHelp auricCells.png|48px|link=Auric Cells]]</div>
        -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ISBG">2700[[File:IconHelp iridescentShards.png|48px|link=Iridescent Shards]]</div>
      -- </div>
    -- </div>
    -- <div class = "expansionButton">
      -- <div class = "divTableHeader displayFlex flexCenter bold"><span id = "outfit1" class = "divButton fakeLink">⇓ Expand ⇓</span></div> <!-- outfit# => variable unique to outfit --> <!-- Secondary text: ⇑ Collapse ⇑ -->
    -- </div>  
  -- </div>
  
  
  
  -- <div class = "expandablePart displayFlex flexColumn outfit1"> <!-- outfit# => variable unique to outfit -->
    -- <div class = "expandedPiece displayFlex piece1">
      -- <div class = "pieceThumbnail displayFlex flexCenter BG-All CosmeticsBG-All SquareBG-1-enh">[[File:DF Head01 CV04.png|230px|link=]]</div>
      -- <div class = "pieceDetailsValues displayFlex flexCenter flexStretch">
        -- <div class = "pieceHeaders displayFlex flexColumn">
          -- <div class = "pieceDetail bodyPartHeader divTableHeader displayFlex flexCenter">Piece</div>
          -- <div class = "pieceDetail nameHeader divTableHeader displayFlex flexCenter">Name</div>
          -- <div class = "pieceDetail collectionHeader divTableHeader displayFlex flexCenter">Collection</div>
        -- </div>
        -- <div class = "pieceHeaders displayFlex flexColumn">
          -- <div class = "pieceDetail bodyPartHeader divTableCell displayFlex flexCenter">[[File:CategoryIcon head.png|56px|link=]]</div>
          -- <div class = "pieceDetail nameHeader divTableCell displayFlex flexCenter">Basic Fitted Cap (Gray)</div>
          -- <div class = "pieceDetail collectionHeader divTableCell displayFlex flexCenter">Dwight Essentials</div>
        -- </div>
        -- <div class = "pieceDesc divTableCell displayFlex flexCenter">piece desc</div>
        -- <div class = "priceContainer displayFlex flexColumn">
          -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ACBG">50[[File:IconHelp auricCells.png|48px|link=Auric Cells]]</div>
          -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ISBG">900[[File:IconHelp iridescentShards.png|48px|link=Iridescent Shards]]</div>
        -- </div>
      -- </div>
    -- </div>
    
    -- <div class = "expandedPiece displayFlex piece2">
      -- <div class = "pieceThumbnail displayFlex flexCenter BG-All CosmeticsBG-All SquareBG-1-enh">[[File:DF Torso01 CV04.png|230px|link=]]</div>
      -- <div class = "pieceDetailsValues displayFlex flexCenter flexStretch">
        -- <div class = "pieceHeaders displayFlex flexColumn">
          -- <div class = "pieceDetail bodyPartHeader divTableHeader displayFlex flexCenter">Piece</div>
          -- <div class = "pieceDetail nameHeader divTableHeader displayFlex flexCenter">Name</div>
          -- <div class = "pieceDetail collectionHeader divTableHeader displayFlex flexCenter">Collection</div>
        -- </div>
        -- <div class = "pieceHeaders displayFlex flexColumn">
          -- <div class = "pieceDetail bodyPartHeader divTableCell displayFlex flexCenter">[[File:CategoryIcon torso.png|56px|link=]]</div>
          -- <div class = "pieceDetail nameHeader divTableCell displayFlex flexCenter">Basic Fitted Cap (Gray)</div>
          -- <div class = "pieceDetail collectionHeader divTableCell displayFlex flexCenter">Dwight Essentials</div>
        -- </div>
        -- <div class = "pieceDesc divTableCell displayFlex flexCenter">piece desc</div>
        -- <div class = "priceContainer displayFlex flexColumn">
          -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ACBG">50[[File:IconHelp auricCells.png|48px|link=Auric Cells]]</div>
          -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ISBG">900[[File:IconHelp iridescentShards.png|48px|link=Iridescent Shards]]</div>
        -- </div>
      -- </div>
    -- </div>
    
    -- <div class = "expandedPiece displayFlex piece3">
      -- <div class = "pieceThumbnail displayFlex flexCenter BG-All CosmeticsBG-All SquareBG-1-enh">[[File:DF Legs01 CV07.png|230px|link=]]</div>
      -- <div class = "pieceDetailsValues displayFlex flexCenter flexStretch">
        -- <div class = "pieceHeaders displayFlex flexColumn">
          -- <div class = "pieceDetail bodyPartHeader divTableHeader displayFlex flexCenter">Piece</div>
          -- <div class = "pieceDetail nameHeader divTableHeader displayFlex flexCenter">Name</div>
          -- <div class = "pieceDetail collectionHeader divTableHeader displayFlex flexCenter">Collection</div>
        -- </div>
        -- <div class = "pieceHeaders displayFlex flexColumn">
          -- <div class = "pieceDetail bodyPartHeader divTableCell displayFlex flexCenter">[[File:CategoryIcon legs.png|56px|link=]]</div>
          -- <div class = "pieceDetail nameHeader divTableCell displayFlex flexCenter">Basic Fitted Cap (Gray)</div>
          -- <div class = "pieceDetail collectionHeader divTableCell displayFlex flexCenter">Dwight Essentials</div>
        -- </div>
        -- <div class = "pieceDesc divTableCell displayFlex flexCenter">piece desc</div>
        -- <div class = "priceContainer displayFlex flexColumn">
          -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ACBG">50[[File:IconHelp auricCells.png|48px|link=Auric Cells]]</div>
          -- <div class = "ccyPrice displayFlex flexCenter flexColumn BG-All ISACBG-All ISBG">900[[File:IconHelp iridescentShards.png|48px|link=Iridescent Shards]]</div>
        -- </div>
      -- </div>
    -- </div>
  -- </div>
-- </div>

return p