Module:DLCs

From Dead by Daylight Wiki
Jump to navigation Jump to search

local p = {}
local utils = require("Module:Utils")
local data = require("Module:Datatable" .. utils.lang())
local mathOps = require("Module:MathOps")
local str = require("Module:Strings")
local ext = require("Module:Extensions")
local various = require("Module:Various")
local c = utils -- As there is onlny one function relevant to colors I make only alias to utils Module to convenient naming "c - color"
local frame = mw.getCurrentFrame()

local _chapterCategories = {1, 2} --DLC categories that have their number index in name
local _checkForLastDlcCapsules = 3
local _defaultCharacterCountInDlcType = {
	[1] = 2, --Full chapters has 2 characters by default
	[2] = 1  --Half chapters has 1 character by default
}

local strings = {
	the = cstr.the,
	noDLC = "DLC wasn't found in DLC table in [[Module:datatable|Datatable Module]]",
	charNotFound = "Character not found",
	noSpecialTheme = "No special Theme Music",
	noSteamLink = "No linked Store Page",
	sp = "Store Page",
	rd = "Release Date",
	cat = "Category",
	cost = "Cost",
	theme = "Theme Music",
	killerTheme = "Killer Theme",
	survTheme = "Survivor Theme",
	chapter = "CHAPTER",
	paragraph = "paragraph",
	halfChapter = "half-chapter",
	retracted = "Retracted",
	retired = "Retired",
	retractedDlc = "Retracted #1# #2#", --name of dlc category will be placed
	retiredDlc = "Retired #1# #2#",
	dlcLink = "Downloadable Content",
	logo = "Logo",
	dlc = "DLC",
	mainArticle = "Main Article", --DLC page text under chapter title
	nSwitch = iclr(4, 'This DLC comes pre-installed on the Nintendo Switch version of the game.'),
	xps = iclr(4, 'This DLC comes pre-installed on the Console versions of the game.'),
	was = "was",
	willBe = "will be",
	released = "released",
	uknownDate = "unknown date",
	forString = "for",
	orString = "or",
	on = "on", -- was released "on" {date}
	inString = "in",
	adds = "adds", -- The DLC "adds" {character(s)}
	andString = "and",
}
----------------------------------------------
if utils.lang() ~= cstr.empty and (counter or 0) < 1 then
	counter = (counter or 0) + 1
	strings = require("Module:DLCs" .. utils.lang()).strings
end

local fileSuffixes = {
		dlcImage = "_main_header", --DLC Image
		themeMusic = "_Theme_Music" --DLC Theme music
}

function p.getLatestDlcId()
	result = 0;
	for _, dlc in ipairs(data.dlcs) do
		if result < dlc.id then result = dlc.id end
	end
	return result
end
p.dlcMinIdForCapsuleCheck = p.getLatestDlcId() - _checkForLastDlcCapsules + 1

--used for truncating the page name (CHAPTER ##: )DLCName to get only DLCName
function p.getShortName(name)
	if name:match(":") ~= nil then
		--mw.log(name:match("^[%a%d ]*: ?(.+)"))
		return name:match("^[%a%d%. ]*: ?(.+)")
	else
		return name
	end
end

--with "Chapter" prefix
function p.getDlcFullNameByCharacter(character)
	local charDlcs = p.getDlcs(character)
	local result = cstr.empty
	for i, dlc in ipairs(charDlcs) do
		result = result .. ((i > 1 and dnl) or cstr.empty) ..
			link(((table.contains(_chapterCategories, dlc.category) and strings.chapter .. space .. p.getDlcOrderNumber(dlc) .. colon .. space) or cstr.empty) .. dlc.name)
	end
	return result
end

function p.getDlcs(character)
	return
		(
			type(character) == types.table
		and type(character.dlc) == types.table 
		and (character.dlc.category and {character.dlc}) --dlc can be already populated as a dlc table
		) 
		or p.getDlcsByCharacter(character)
end

--called directly from wiki
function p.getMainDlcFullNameByCharacter(character)
	character = utils.getCharacterByName(utils.resolveParameter(character))
	if character then
		character.dlc = p.getMainDlc(p.getDlcsByCharacter(character))
		return p.getDlcFullNameByCharacter(character)
	end
	return strings.charNotFound
end

function p.getDlcByCharacter(character) return p.getDlcsByCharacter(character) end
function p.getDlcsByCharacter(character)
	if not character or not character.name then
		character = utils.getCharacterByName(utils.resolveParameter(character))
	end
	local result = {}
	
	if character then
		if type(character.dlc) == types.table then
			if not table.empty(character.dlc) then
				if type(character.dlc[1]) == types.table then
					result = character.dlc
				elseif type(character.dlc[1]) == types.number then
					for _, dlcId in ipairs(character.dlc) do
						table.add(result, p.getDlcById(dlcId))
					end
				end
			end
		elseif type(character.dlc) == types.number then
			table.add(result, p.getDlcById(character.dlc))
		end
	end
	return result
end

function p.isDiscounted(dlcList)
	halvingDlcCategories = {8}
	if dlcList.category and table.contains(halvingDlcCategories, dlcList[1].category) then
		return true
	else
		for _, dlc in ipairs(dlcList) do
			if table.contains(halvingDlcCategories, dlc.category) then
				return true
			end
		end
	end
	return false
end
function p.getChapterPackDlc(dlcList)
	for _, dlc in ipairs(dlcList) do
		if dlc.category == 8 then return dlc end
	end
end

function p.getDlcOrderNumber(dlc)
	dlc = utils.resolveParameter(dlc)
	if dlc.category == 1 then --chapter
		return getChapterNumber(dlc)
	elseif dlc.category == 2 then --paragraph
		return getParagraphNumber(dlc)
	end
	
	return 0	
end

function p.getDlcOrderNumberByCharacter(character)
	local charDlcs = p.getDlcsByCharacter(character)
	for _, dlc in ipairs(charDlcs) do
		if table.contains(_chapterCategories, dlc.category) then
			return p.getDlcOrderNumber(dlc)
		end
	end
	return 0
end

function getChapterNumber(dlc)
	local counter = 0
	for _, dlcItem in ipairs(data.dlcs) do
		if dlcItem.category == 1 then
			counter = counter + 1
			if dlc.id == dlcItem.id then
				return counter
			end
		end
	end
end

function getParagraphNumber(dlc)
	local counter = 0
	local previousChapterFound = false
	local dlcIndex = 0
	
	for i, dlcItem in ipairs(data.dlcs) do
		if dlcItem.id == dlc.id then
			dlcIndex = i
			break
		end
	end
	
	while not previousChapterFound and dlcIndex > 0 do
		if data.dlcs[dlcIndex].category == 1 then
			return getChapterNumber(data.dlcs[dlcIndex]) + 0.5
		end
		dlcIndex = dlcIndex - 1
	end
end

function resolveDlcParameter(dlc)
	if dlc == nil then
		return p.getDlcByName(mw.title.getCurrentTitle().text)
	elseif type(dlc) == "table" then
		if dlc.args[1] == nil then 
			return p.getDlcByName(mw.title.getCurrentTitle().text) --none parameter => calling from page
		end
		if type(dlc.args[1]) == nil then 
			return p.getDlcByName(p.getShortName(mw.title.getCurrentTitle().text))
		elseif type(dlc.args[1]) == "string" then
			if tonumber(dlc.args[1]) == nil then 
				return p.getDlcByName(dlc.args[1])
			else 
				return p.getDlcById(tonumber(dlc.args[1]))
			end
		end
	elseif type(dlc) == "string" then
		return p.getDlcByName(dlc)
	elseif type(dlc) == "number" then
		return p.getDlcById(dlc)
	end
end

function p.getMainDlc(dlcList)
	if (dlcList and dlcList.category) then --dlcList is actually a sole DLC object
		if table.contains(_chapterCategories, dlcList.category) then
			return dlcList
		else
			return nil
		end
	end
	for _, dlc in ipairs(dlcList or {}) do
		if table.contains(_chapterCategories, dlc.category) then
			return dlc
		end
	end
end
function p.getCharacterMainDlc(character)
	return p.getMainDlc(p.getDlcs(character))
end
function p.getDlcByName(name)
	for _, dlc in ipairs(data.dlcs) do
		if name == dlc.name then return dlc end
	end
	name = p.getShortName(name) --if the name was passed with CHAPTER prefix, remove it
	for _, dlc in ipairs(data.dlcs) do
		if name == dlc.name then return dlc end
	end
end

function p.getDlcById(id)
	for _, dlc in ipairs(data.dlcs) do
		if id == dlc.id then 
			local dlcObj = table.copy(dlc)
			return dlcObj
		end
	end
end

function p.getCountOfDlcs()
	return utils.getCount("dlc")
end

function p.getCountOfChapters()
	return utils.getCount("chapter")
end

function p.getCountOfParagraphs()
	return utils.getCount("paragraph")
end

function p.getCountOfClothingDlcs()
	return utils.getCount("clothing")
end

function p.getCountOfSoundtracks()
	return utils.getCount("ost")
end

function p.getCountOfCharacterDlcs()
	return utils.getCount("character")
end

function getCostTable(dlc)
	local result = cstr.empty
	
	if type(dlc.cost) == types.string then
		return tl .. dlc.cost	
	end
	if dlc.cost == false then
		return false
	end
	if dlc.cost == nil then
		dlc = populateDefaultCosts(dlc)
		if not dlc.cost then return result end
	end
	for i, cost in ipairs(dlc.cost) do
		local ccy = getCurrencyById(cost.ccy)
		
		if cost.value == 0 then
			cost.value = "-"
		end
		result = result .. tl
		if ccy.gc then
			result = result .. utils.commaFormat(cost.value) .. space .. utils.IconLink(ccy.plural or ccy.name .. "s") -- #1: calling IconLink template #2: the 's' makes plural version of currency,
		else
			result = result .. ccy.symbol .. cost.value
		end
		if dlc.cost[i + 1] ~= nil then
			result = result .. nl .. ntl .. nl
		end
	end
	
	return result
end

function p.resolveDlcTableMainPage()
	local result = cstr.empty
	local name, fileName, linkPage
	local lastCategory = 0
	local closingResolve, offset
	
	utils.sortDlcByCategory(data.dlcs)
	local latestId = p.getLatestDlcId()
	
	result = result .. 
		'<div class="fpbox" id="fpDlcs" style="text-align: center;">' ..
			'<div class="heading">' .. link(strings.dlcLink) .. '</div>' ..
			'<div class="fplinks">'
	for i, dlc in ipairs(data.dlcs) do
		if not dlc.skip then
			if dlc.category ~= lastCategory then
				lastCategory = dlc.category
				result = result .. 
					'<div class = "dlcCategorySection">' ..
						'<div class = "categoryLabel">' .. resolveDlcCategory(dlc) .. '</div>' ..
						'<div class = "categoryDlcs">'
			end
			name = dlc.name or strings.noDLC
			fileName = p.resolveDlcCapsuleFileNameByDlc(dlc, latestId - _checkForLastDlcCapsules + 1) -- "-1" = check file for last TWO DLCs; [Latest DLC ID] - number of last DLC + 1 => _checkForLastDlcCapsules = 3 => last 3 DLC are checked
			linkPage = name .. (dlc.multiName and space .. brackets(strings.dlc) or cstr.empty)
			
			--Capsule
			result = result .. 
					'<div class = "displayFlex dlcCapsule relative">' ..
						'<div class = "displayFlex dlcCapsuleImageContainer relative">' ..
							'<div class = "dlcBorder absolute"></div>' ..
							'<div class = "dlcLightEffect absolute"></div>' ..
							'<div class = "dlcCpasuleImg relative">' .. file(fileName, 'link=' .. linkPage) .. '</div>' ..
						'</div>' ..
						'<div class = "dlcLink displayFlex">' .. link(linkPage, name) .. '</div>' ..
					'</div>'

			closingResolved = false
			offset = 1
			repeat
				local futureDlc = data.dlcs[i+offset]
				--if the DLC is last, or any other following DLC(s) are skip = true, or simply current DLC and upcoming NON-skip DLC are different category
				if dlc.id == data.dlcs[#data.dlcs].id or (futureDlc.skip and futureDlc.id == data.dlcs[#data.dlcs].id) or (dlc.category ~= futureDlc.category and not futureDlc.skip) then
					closingResolved = true
					result = result .. 
						'</div>' .. --closing ".categoryDlcs"
					'</div>' -- closing ".dlcCategorySection"
				elseif dlc.category == futureDlc.category and not futureDlc.skip then
					closingResolved = true
				else
					offset = offset + 1
				end
			until closingResolved or offset + i > #data.dlcs
		end
	end
	result = result .. 
			'</div>' ..
		'</div>'

	return result
end

function p.resolveDlcCapsuleFileNameByDlc(dlc, latestId)
	local fileConst = "Capsule"
	local fileName = utils.resolveFileName(dlc.name) .. fileConst
	
	if dlc.id >= latestId and not utils.isValidFileName(fileName, cstr.png) then
		return 'UnknownDLC.png'
	end

	return fileName .. dot .. cstr.png
end

--currently works only for Chapters and Half-Chapters
function populateDefaultCosts(dlc)
	local result = nil
	local dlcChars = utils.getCharsByDlc(dlc)
	
	if not (dlc.retracted or dlc.retired) and (dlc.category == 1 or dlc.category == 2) then
		result = {}
		local dlcCharCount = (#dlcChars > 0 and #dlcChars) or _defaultCharacterCountInDlcType[dlc.category]
		table.insert(result, {ccy = 1, value = data.dlcCosts[dlc.category][dlcCharCount]}) --Dollar
		table.insert(result, {ccy = 5, value = data.charACCost * dlcCharCount}) -- Auric Cells
		if not dlc.licensed then
			table.insert(result, {ccy = 3, value = data.charISCost  * dlcCharCount}) -- IS Cost per character
		end
	end
	dlc.cost = result
	return dlc
end

function getCountDlcCosts(dlc)
	if type(dlc.cost) == "string" then return 1 end
	return #dlc.cost
end

function getCurrencyById(id)
	for _, ccyItem in ipairs(ccy) do
		if ccyItem.id == id then return ccyItem end
	end
end

--[[
function p.resolveThemeMusic(dlc)
	return resolveThemeMusic(p.getDlcById(dlc))
end]]
--Stranger Things Theme Music.ogg
function resolveThemeMusic(dlc)
	local dlcChars = utils.getCharsByDlc(dlc)
	local survTheme
	local killerTheme
	
	for _, character in ipairs(dlcChars) do
		if utils.isKiller(character) and not killerTheme then
			killerTheme = various.resolveCharacterThemeMusic(character)
		elseif not survTheme then
			survTheme =  various.resolveCharacterThemeMusic(character)
		end
		if killerTheme and survTheme then break end
	end
	
	return survTheme, killerTheme
	
	--[[
	lg(dlc.name)
	local name = utils.resolveFileName(utils.CapitalizeName(utils.RemoveSpecialCharacters(dlc.name)), true, true) .. fileSuffixes.themeMusic
	lg(name)

	for _, theme in ipairs(dlcThemes) do
		if theme.id == dlc.id then
			name = theme.fileName
		end
	end
	local valid = utils.isValidFileName(name, cstr.ogg)
	return (valid and link(cstr.file .. name .. dot .. cstr.ogg)) or strings.noSpecialTheme
	]]
end

function resolveShopLink(dlc)
	if dlc.link ~= nil and type(dlc.link) == types.number and dlc.link > 0 then
		return "[https://store.steampowered.com/app/" .. dlc.link .. " " .. strings.sp .. "]"
	end
	return strings.noSteamLink
end

function resolveDlcCategory(dlc)
	if dlc.retracted then
		return utils.getDynamicString(dlcCategories[dlc.category], strings.retractedDlc)
	elseif dlc.retired then
		return utils.getDynamicString(dlcCategories[dlc.category], strings.retiredDlc)
	else
		return dlcCategories[dlc.category]
	end
end

function p.getImageFileNameByDlc(id) mw.log(getImageFileNameByDlc(p.getDlcById(id))) end --dev remove

function getImageFileNameByDlc(dlc)
	for _, dlcImage in ipairs(dlcImages) do
		if dlc.id == dlcImage.id then
			return utils.resolveImageName(dlcImage.cover)
		end
	end
	return utils.resolveImageName(utils.resolveFileName(dlc.name) .. fileSuffixes.dlcImage)
end

function p.resolveDlcTable(dlc)
	dlc = resolveDlcParameter(dlc)
	if dlc == nil then return bclr("gold", strings.noDLC) .. nl end
	local result = cstr.empty
	local costTable = getCostTable(dlc)
	local survTheme, killerTheme = resolveThemeMusic(dlc)

	result = result .. 
		'{| class="wikitable" style="float:right;"' .. nl ..
		ntl .. 'style="font-size: 24px;"' .. nl ..
		hl .. 'width="350px" align="center" colspan="2"' .. tl .. b(utils.replaceLastSpaceByNBSP(dlc.name)) .. nl .. ntl .. nl ..
		tl .. 'class="center" width="350px" height="50px" colspan="2"' .. tl .. file(getImageFileNameByDlc(dlc), '300px') .. nl .. ntl .. nl ..
		tl .. 'width="100px"' .. tl .. b(strings.rd) .. dtl .. 'width="200px"' .. tl .. utils.resolveDateTime(dlc.rDate) .. nl .. ntl .. nl
	if dlc.retired then
		result = result .. tl .. b(strings.retired) .. nl .. tl .. utils.resolveDateTime(dlc.retired) .. nl .. ntl .. nl
	end
	if dlc.retracted then
		result = result .. tl .. b(strings.retracted) .. nl .. tl .. utils.resolveDateTime(dlc.retracted) .. nl .. ntl .. nl
	end
	result = result ..
		tl .. b(strings.cat) .. dtl .. 'width="200px"' .. tl .. resolveDlcCategory(dlc) .. nl .. ntl .. nl ..
		((dlc.cost and 
			tl .. 'rowspan = ' .. getCountDlcCosts(dlc) .. tl .. b(strings.cost) .. dtl .. 'width="200px" ' .. costTable .. nl .. ntl .. nl) or cstr.empty)
	
	if killerTheme or survTheme then
		result = result ..
			((killerTheme and tl .. b((killerTheme and not survTheme and strings.theme or strings.killerTheme)) .. dtl .. 'width="200px"' .. tl .. killerTheme .. nl .. ntl .. nl) or cstr.empty) ..
			((survTheme and tl .. b(strings.survTheme) .. dtl .. 'width="200px"' .. tl .. survTheme .. nl .. ntl .. nl) or cstr.empty)
	end
	result = result ..
		hl .. 'align="center" colspan="2"' .. tl .. resolveShopLink(dlc) .. nl ..
		"|}"

	mw.log(result)
	return result
end

function p.getListOfDlcChapters()
	local result = cstr.empty;
	local counter = 1;
	
	for _, dlcItem in ipairs(data.dlcs) do
		if dlcItem.category == 1 then
			result = result .. nbullet .. link(strings.chapter .. space .. counter .. colon .. space .. dlcItem.name) .. nl
			counter = counter + 1
		end
	end
	
	mw.log(result)
	return result
end


--If the iconlink for currrency doesn't work, add new field 'plural' in ccy table in Datatable module: plural = "Bloodpoints" (for Bloodpoint currency)
function p.resolveDlcPage(args)
	local dlcTypes = resolveDlcTypes(utils.resolveParameter(args))
	local header = utils.resolveParameter(args):upper()
	header = (header == strings.halfChapter:upper() and strings.chapter) or header
	local result = cstr.empty
	
	utils.sortDlcByCategory(data.dlcs)
	local counter = 1
	for _, dlc in ipairs(data.dlcs) do
		for _, dlcType in ipairs(dlcTypes) do
			if dlc.category == dlcType then
				result = result .. getDlcArticle(dlc, counter, header)
				counter = counter + 1
				break	
			end
		end
	end
	
	mw.log(result)
	return result	
end

function getDlcArticle(dlc, number, header)
	return
		'<h4> ' .. header .. space ..  number .. colon .. space .. dlc.name .. ' </h4>' .. nl ..
		file(getDlcLogo(dlc) .. dot .. cstr.png, 'thumb', '400x150px') .. nlp ..
		i(strings.mainArticle .. colon .. space .. link((dlc.name .. (dlc.multiName and space .. brackets(strings.dlc) or cstr.empty)))) .. nlp ..
		the(dlc) .. space .. b(dlc.name) .. space .. strings.dlc .. space .. getDlcTime(dlc) .. space .. getDlcCosts(dlc) .. space .. getDlcReleaseDate(dlc) .. dot .. nlp ..
		((#utils.getCharsByDlc(dlc) > 0 and the(strings.dlc) .. space .. strings.dlc .. space .. strings.adds ..space .. getCharacterString(dlc) .. dot .. dnl) or cstr.empty) .. --if there are no characters skip the line completely
		((dlc.flags and getAdditionalNote(dlc)) or cstr.empty)
end

function getDlcLogo(dlc)
	return strings.logo .. space .. utils.FirstLetterLower(utils.resolveFileName(dlc.name, false, true))
end

function getDlcTime(dlc)
	result = strings.released
	if dlc.rDate ~= nil and utils.IsFullDateTime(dlc.rDate) and utils.toTimestamp(dlc.rDate) < utils.today() then
		result = strings.was .. space .. result
	else
		if utils.getDatePart(dlc.rDate, "month") and utils.getMonth(dlc.rDate) < utils.getMonth(utils.today()) then
			result = strings.was .. space .. result
		else
			result = strings.willBe .. space .. result
		end
	end
	return result
end

function getDlcReleaseDate(dlc)
	result = cstr.empty
	if dlc.rDate ~= nil then
		result = ((utils.IsFullDateTime(dlc.rDate) and strings.on) or strings.inString) .. space .. utils.resolveDateTime(dlc.rDate, true)
	end
	return result
end

function getDlcCosts(dlc)
	if type(dlc.cost) == types.string then return strings.forString .. space .. dlc.cost end
	local result = cstr.empty
	
	if dlc.cost == nil then
		populateDefaultCosts(dlc)
	end
	if dlc.cost then --if it not nill or not false, then process costs
		if #dlc.cost > 0 then
			result = result .. strings.forString .. space
		end
		
		utils.sortRealCcyFirst(dlc.cost)
		for i, cost in ipairs(dlc.cost) do
			local currency = utils.getCcyById(cost.ccy)
			if not currency.gc then --real currency
				result = result .. currency.symbol .. cost.value
			else --game currency
				result = result .. utils.commaFormat(cost.value) .. space .. utils.IconLink(currency.plural or currency.name .. 's')
			end
			if i < #dlc.cost then --add ' or ' if there will be more currencies
				result = result .. space .. strings.orString .. space
			end
		end
	end
	return result
end

function getCharacterString(dlc)
	local characters = utils.getCharsByDlc(dlc)
	local result = cstr.empty
	
	utils.sortCharsKillersFirst(characters)
	for i, character in ipairs(characters) do
		--result = result .. utils.IconLink((not character.power and character.shortName or character.name) or character.name)
		result = result .. utils.IconLink(
			(utils.isKiller(character) and character.name .. ((character.multiName and space .. brackets(ils.killer)) or cstr.empty)) or character.shortName or character.name,
			((utils.isKiller(character) and the(character)) or cstr.empty) .. character.name
		)

		if i + 1 == #characters then
			result = result .. space .. strings.andString .. space
		elseif i < #characters then --add comma if there will be more currencies
			result = result .. comma .. space
		end
	end
	return result
end

function getAdditionalNote(dlc)
	local result = cstr.empty
	for _, flag in ipairs(dlc.flags) do
		if		flag == "xps" then result = result .. strings.xps .. dnl
		elseif	flag == "switch" then result = result .. strings.nSwitch .. dnl
		end
	end
	return result
end

function resolveDlcTypes(dlcTypeString)
	if		dlcTypeString == strings.chapter:lower() then return {1, 7}
	elseif	dlcTypeString == strings.halfChapter:lower() then return {2}
	--elseif	dlcTypeString == "clothing pack" then return {3}
	--elseif	dlcTypeString == "soundtrack" then return {4}
	--elseif	dlcTypeString == "character pack" then return {5}
	elseif	dlcTypeString == "other" then return {6}
	end
	return {0} --not found
end

return p